Java 8 stream - merge collections of objects sharing the same Id
If you are OK returning a Collection
it would look like this:
Collection<Invoice> invoices = list.collect(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
left.setAmount(left.getAmount().add(right.getAmount()));
return left;
})).values();
If you really need a List
:
list.stream().collect(Collectors.collectingAndThen(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
left.setAmount(left.getAmount().add(right.getAmount()));
return left;
}), m -> new ArrayList<>(m.values())));
Both obviously assume that Invoice
is mutable...
If you could add the following copy constructor and merge method to your Invoice
class:
public Invoice(Invoice another) {
this.month = another.month;
this.amount = another.amount;
}
public Invoice merge(Invoice another) {
amount = amount.add(another.amount); // BigDecimal is immutable
return this;
}
You could reduce as you want, as follows:
Collection<Invoice> result = list.stream()
.collect(Collectors.toMap(
Invoice::getMonth, // use month as key
Invoice::new, // use copy constructor => don't mutate original invoices
Invoice::merge)) // merge invoices with same month
.values();
I'm using Collectors.toMap
to do the job, which has three arguments: a function that maps elements of the stream to keys, a function that maps elements of the stream to values and a merge function that is used to combine values when there are collisions on the keys.
Collection<Invoice> result = invoices.stream().collect(groupingBy(i -> i.month,
collectingAndThen(
reducing((Invoice i1, Invoice i2) -> new Invoice(i1.month, i1.amount + i2.amount)),
Optional::get))).values();