Должен ли я использовать Java 8 Streams Api для объединения двух коллекций?

У меня есть такая ситуация, когда кажется, что API Java 8 Streams будет полезен, но я не совсем уверен, как это может быть.

Из двух коллекций с разными типами элементов я хочу построить третий коллекцию, элементы которой являются всеми возможными парами элементов из обеих коллекций. В основном:

Два разных типа элементов...

public class A {}
public class B {}

A "пара" As и Bs.

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

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

"Комбинация", выполненная с использованием API java.util.Collection в старом стиле:

 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;
 }

Порядок в результирующей коллекции пар не важен. Таким образом, каждый экземпляр пары может быть создан и добавлен к полученной коллекции параллельно. Как я мог достичь этого?

Лучшее, что я смог выяснить сам, заключалось в использовании версии Streams foreach:

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

Этот пример был упрощен для упрощения. Класс Pair является примером обработки двух элементов в третьем (т.е. A 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);

Ответы

Ответ 1

Надеюсь, у меня нет глупых опечаток, но в основном вы можете сделать это:

List<Pair> list = as
                  .stream()
                  .flatMap(a -> bs.stream().map (b -> new Pair(a,b)))
                  .collect (Collectors.toList());
  • Сначала вы создаете Stream<A> из as.
  • Для каждого экземпляра a 2.1 Создайте Stream<B> из bs
    2.2. Сопоставьте каждый b с парой (a,b)
  • Сгладить все пары до одного потока.
  • Наконец, я собрал их в список, хотя вы можете выбрать другие коллекции.

Ответ 2

Если вы открыты для использования сторонней библиотеки, вы можете использовать Коллекции Eclipse Sets.cartesianProduct(). Это потребует, чтобы ваши a и b были как Sets. 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 не являются наборами, вы можете использовать CollectionAdapter flatCollect и collect, которые эквивалентны flatMap и map на 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, было бы определение вашего собственного Collector для cartesianProduct. Это сложнее, чем другое решение 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.