I have two objects like following:
public class A {
private Integer id;
private String name;
private List list;
public A(Integer id
If you don't want to use extra functions you can do the following, it's readable and easy to understand, first group by id, create a new object with the first element in the list and then join all the B's classes to finally collect the A's.
List result = list.stream()
.collect(Collectors.groupingBy(A::getId))
.values().stream()
.map(grouped -> new A(grouped.get(0).getId(), grouped.get(0).getName(),
grouped.stream().map(A::getList).flatMap(List::stream)
.collect(Collectors.toList())))
.collect(Collectors.toList());
Another way is to use a binary operator and the Collectors.groupingBy method. Here you use the java 8 optional class to create the new A the first time when fst is null.
BinaryOperator joiner = (fst, snd) -> Optional.ofNullable(fst)
.map(cur -> { cur.getList().addAll(snd.getList()); return cur; })
.orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));
Collection result = list.stream()
.collect(Collectors.groupingBy(A::getId, Collectors.reducing(null, joiner)))
.values();
If you don't like to use return in short lambdas (doesn't look that well) the only option is a filter because java does not provide another method like stream's peek (note: some IDEs highlight to 'simplify' the expression and mutations shouldn't be made in filter [but i think in maps neither]).
BinaryOperator joiner = (fst, snd) -> Optional.ofNullable(fst)
.filter(cur -> cur.getList().addAll(snd.getList()) || true)
.orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));
You can also use this joiner as a generic method and create a left to right reducer with a consumer that allows to join the new mutable object created with the initializer function.
public class Reducer {
public static Collector reduce(Function initializer,
BiConsumer combiner) {
return Collectors.reducing(null, (fst, snd) -> Optional.ofNullable(fst)
.map(cur -> { combiner.accept(cur, snd); return cur; })
.orElseGet(() -> initializer.apply(snd)));
}
public static Collector reduce(Supplier supplier,
BiConsumer combiner) {
return reduce((ign) -> supplier.get(), combiner);
}
}
And use it like
Collection result = list.stream()
.collect(Collectors.groupingBy(A::getId, Reducer.reduce(
(cur) -> new A(cur.getId(), cur.getName(), new ArrayList<>(cur.getList())),
(fst, snd) -> fst.getList().addAll(snd.getList())
))).values();
Or like if you have an empty constructor that initializes the collections
Collection result = list.stream()
.collect(Collectors.groupingBy(A::getId, Reducer.reduce(A::new,
(fst, snd) -> {
fst.getList().addAll(snd.getList());
fst.setId(snd.getId());
fst.setName(snd.getName());
}
))).values();
Finally, if you already have the copy constructor or the merge method mentioned in the other answers you can simplify the code even more or use the Collectors.toMap method.