Должен ли я использовать 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.