【问题标题】:Should I use Java 8 Streams Api to combine two Collections?我应该使用 Java 8 Streams Api 来组合两个集合吗?
【发布时间】:2014-07-30 22:27:53
【问题描述】:

我遇到这种情况,Java 8 Streams API 似乎会有所帮助,但我不完全确定它会如何。

从具有不同元素类型的两个集合中,我想构建第三个集合,其元素是两个集合中所有可能的元素。基本上:

两种不同的元素类型...

public class A {}
public class B {}

一对“A”和“B”。

public class Pair {
   private A a;
   private B b;

   public Pair(A a, B b){
     this a = a;
     this b = b;
   }
}

使用旧式java.util.Collection API 制作的“组合”:

 public Collection<Pair> combine(Collection<A> as, Collection<B> bs){
    Collection<Pair> pairs = new ArrayList();
    foreach(A a: as){
      foreach(B b: bs){
          Pair pair = new Pair(a,b);
          pairs.add(pair);
      }
    }
    return pairs;
 }

结果对集合中的顺序并不重要。因此,可以并行创建 Pair 的每个实例并将其添加到结果集合中。我怎样才能做到这一点?

我自己能想到的最好办法就是使用 foreach 的 Streams 版本:

as.foreach(
  a -> {
    bs.foreach(
      b -> {
          Pair pair = new Pair(a,b);
          pairs.add(pair);
      }
  }
);

为了简化起见,这个例子是微不足道的。 Pair 类是将两个元素处理为第三个元素(即java.util.function.BiFunction)的示例,并将它们添加到Collection 只是可变归约的一个示例。

有没有更优雅的方法来做到这一点?或者更可取,在效率方面以更有利可图的方式?类似的东西

BiFunction<A,B,Pair> combinator = Pair::new; //or any other function f(a,b)=c;

Stream<Pair> pairStream = 
  Streams.unknownElegantMethod(as.stream(), bs.stream(), combinator);

【问题讨论】:

  • 与问题无关,但不是(a,b) -&gt; {return new Pair(a,b)}; use Pair::new. And for the question, stream are usable once. And you Java code shows that you are using a stream more than one time (the one on bs`)。
  • 关于Pair:new 的好点,我已经更新了这个问题。第二点是错误的,因为bsCollection,而不是流。
  • 不,我明白了。我的意思是当你用完一个流时,你就不能再使用它了。因此,当且仅当您想要所有可能的 (A, B) 对时,解决方案不能或永远不会像 Stream&lt;Pair&gt; pairStream = Streams.unknownElegantMethod(as.stream(), bs.stream(), combinator); 那样。使用flatMap(如答案)或foreach(如您的代码)是唯一的解决方案。

标签: java lambda java-8 java-stream


【解决方案1】:

我希望我没有任何愚蠢的错别字,但基本上你可以做的是:

List<Pair> list = as
                  .stream()
                  .flatMap(a -> bs.stream().map (b -> new Pair(a,b)))
                  .collect (Collectors.toList());
  1. 首先从as 创建一个Stream&lt;A&gt;
  2. 对于每个 a 实例
    2.1 创建Stream&lt;B&gt;bs
    2.2 将每个b映射到一对(a,b)
  3. 将所有对展平为一个流。
  4. 最后我将它们收集到一个列表中,尽管您可以选择其他集合。

【讨论】:

  • 错字:bs,不是b,是b的集合的名字。 SE不会让我做这个有用的编辑:(
  • 编译时,我得到“类型不匹配:无法从 List 转换为 List”。 flatMap 似乎没有从 lambda 获得正确的类型。我用List&lt;Pair&gt; list = as.stream().flatMap(a -&gt; bs.stream().map (b -&gt; new Pair(a,b)).collect(Collectors.toList()).stream()).collect(Collectors.toList()); 修复了它,但修复似乎有点临时,因为它将流转换为集合,然后再次转换回流。
  • 这称为“笛卡尔”或“交叉”连接
【解决方案2】:

如果您愿意使用第三方库,可以使用Eclipse CollectionsSets.cartesianProduct()。这将要求您的 a 和 b 都是 Set。 Eclipse Collections 有一个内置的 Pair 类型,因此您不需要创建它。

public class A {}
public class B {}

public List<Pair<A, B>> combine(Set<A> as, Set<B> bs)
{
    return Sets.cartesianProduct(as, bs).toList();
}

如果您的 a 和 b 不是 Sets,那么您可以使用 CollectionAdapter flatCollectcollect,它们等效于 flatMapmap 上的 Stream

public Collection<Pair<A, B>> combine(Collection<A> as, Collection<B> bs)
{
    MutableCollection<B> adaptB = CollectionAdapter.adapt(bs);
    return CollectionAdapter.adapt(as)
            .flatCollect(a -> adaptB.asLazy().collect(b -> Tuples.pair(a, b)));
}

使用Stream 的另一个可能选项是为cartesianProduct 定义您自己的Collector。这比其他Stream 解决方案更复杂,并且只有在您的代码中多次使用cartesianProduct 时才会有用。

List<Pair<A, B>> pairs = as.stream().collect(cartesianProduct(bs));

public static <T1, T2> Collector<T1, ?, List<Pair<T1, T2>>> 
    cartesianProduct(Collection<T2> other)
{
    return Collector.of(
            ArrayList::new,
            (list, a) -> list.addAll(
                other.stream().map(b -> new Pair(a, b))).collect(Collectors.toList())),
            (list1, list2) ->
            {
                list1.addAll(list2);
                return list1;
            },
            Collector.Characteristics.UNORDERED
    );
}

注意:我是Eclipse Collections 的提交者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-25
    • 2019-10-03
    • 2018-05-10
    • 2017-11-09
    • 1970-01-01
    • 2015-08-03
    • 2020-01-18
    • 1970-01-01
    相关资源
    最近更新 更多