Aggregate List<X> to List<X> with Java 8 Stream API

java, java-8, java-stream

Solution

You can use a `groupingBy` collector to group the objects by `CurrencyUnit`. Without second argument, the `groupingBy` method collects the elements into a list. However, you can also specify a downstream collector if you need something else.

You can use `Collectors::summingInt` and `Collectors::summingLong` for `int` and `long`. For `BigDecimal`, you can fall back to `Collectors::reducing`:

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.reducing;

Map<CurrencyUnit, BigDecimal> result = moneyList.stream()
    .collect(
        groupingBy(
            Money::getCurrencyUnit,
            reducing(BigDecimal.ZERO, Money::getAmount, BigDecimal::add)));

Edit: You can also create `List<Money>`:

List<Money> result = moneyList.stream()
    .collect(
        groupingBy(
            Money::getCurrencyUnit,
            reducing(BigDecimal.ZERO, Money::getAmount, BigDecimal::add)))
    .entrySet().stream()
    .map(e -> new Money(e.getKey(), e.getValue())
    .collect(toList());

Problem

I've got the following class: ``` class Money { CurrencyUnit currencyUnit; BigDecimal amount; } ``` In my application, I get some random list of `Money` objects: ``` currencyUnit | amount --------------------- EUR | 5.1 EUR | 0 USD | 1.09 EUR | 42 USD | 3 ``` Now I'd like to use the Java 8 Stream API to create the following result (simply calling `BigDecimal::add` for each currencyUnit's amount): ``` currencyUnit | amount --------------------- EUR | 47.1 USD | 4.09 ``` What I already know/did: ``` Stream<Money> moneyStream = moneyList.stream(); ``` And here it ends already. I know I can use a `Collector` to produce a `Map<CurrencyUnit, List<Money>>`: ``` moneyStream.collect(Collectors.groupingBy(m -> m.getCurrencyUnit()); ``` But then I still have to go through all key-value-pairs and sum up the amount data. What's the right (and possibly easiest way) to do that? It can't be that complicated, right? `:)` EDIT: If it's not that clear what I need, here's my old-way Java code: ``` Map<CurrencyUnit, Money> map = new HashMap<>(); moneyList.stream().forEach(e -> { Money m = map.get(e.getCurrencyUnit()); if(m == null) { m = new Money(); m.setAmount(BigDecimal.ZERO); m.setCurrencyUnit(e.getCurrencyUnit()); map.put(e.getCurrencyUnit(), m); } m.setAmount(m.getAmount().add(e.getAmount())); }); return map.values(); ``` EDIT 2: Another solution, which isn't really elegant: ``` List<Money> list = inputList.stream() .collect(Collectors.groupingBy(Money::getCurrencyUnit)) .values().stream().map(ml -> { Money money = new Money(); ml.forEach(m -> { if(money.getCurrencyUnit() == null) { money.setCurrencyUnit(m.getCurrencyUnit()); money.setAmount(m.getAmount()); } else { money.setAmount(money.getAmount().add(m.getAmount())); } }); return money; }).collect(Collectors.toList()); ```

Original source