Search code examples
javalambdajava-8java-streamcollectors

Java 8 Lambda - Grouping & Reducing Object


I have a list of Transactions whom I wanted to :

  • First Group by year
  • Then Group by type for every transaction in that year
  • Then convert the Transactions to Result object having sum of all transaction's value in sub groups.

My Code snippets looks like :

Map<Integer, Map<String, Result> res = transactions.stream().collect(Collectors
                            .groupingBy(Transaction::getYear,
                                groupingBy(Transaction::getType),
                                  reducing((a,b)-> new Result("YEAR_TYPE", a.getAmount() + b.getAmount()))
));

Transaction Class :

class Transaction {

    private int year;
    private String type;
    private int value;
}

Result Class :

class Result {

    private String group;
    private int amount;
}

it seems to be not working, what should I do to fix this making sure it works on parallel streams too?


Solution

  • In the context, Collectors.reducing would help you reduce two Transaction objects into a final object of the same type. In your existing code what you could have done to map to Result type was to use Collectors.mapping and then trying to reduce it.

    But reducing without an identity provides and Optional wrapped value for a possible absence. Hence your code would have looked like ;

    Map<Integer, Map<String, Optional<Result>>> res = transactions.stream()
            .collect(Collectors.groupingBy(Transaction::getYear,
                    Collectors.groupingBy(Transaction::getType,
                            Collectors.mapping(t -> new Result("YEAR_TYPE", t.getValue()),
                                    Collectors.reducing((a, b) ->
                                            new Result(a.getGroup(), a.getAmount() + b.getAmount()))))));
    

    to thanks to Holger, one can simplify this further

    …and instead of Collectors.mapping(func, Collectors.reducing(op)) you can use Collectors.reducing(id, func, op)


    Instead of using this and a combination of Collectors.grouping and Collectors.reducing, transform the logic to use Collectors.toMap as:

    Map<Integer, Map<String, Result>> result = transactions.stream()
            .collect(Collectors.groupingBy(Transaction::getYear,
                    Collectors.toMap(Transaction::getType,
                            t -> new Result("YEAR_TYPE", t.getValue()),
                            (a, b) -> new Result(a.getGroup(), a.getAmount() + b.getAmount()))));
    

    The answer would stand complete with a follow-up read over Java Streams: Replacing groupingBy and reducing by toMap.