Search code examples
javajava-streamgroupingby

Java Streams API - Impose Ordering while collecting with groupingBy()


I am trying to group the following list of Foo objects by getName() and count occurrences while ordering by getNumber() in ascending order.

Input:

List<Foo> fooList = List.of(
    new Foo("Alpha", 1.1f),
    new Foo("Delta", 1.2f),
    new Foo("Charlie", 3.1f),
    new Foo("Alpha", 2.1f),
    new Foo("Charlie", 4.1f),
    new Foo("Delta", 2.2f)
);

Map Output:

{Alpha=2, Delta=2, Charlie=2}

My current solution is as follows:

Map<String, Integer> map = new LinkedHashMap<>();

fooList
    .stream()
    .sorted(Comparator.comparingDouble(Foo::getNumber))
    .forEach(foo -> {
        int val = map.getOrDefault(foo.getName(), 0);
        map.put(foo.getName(), val + 1); 
    });

Is there any better way with Collectors.groupingBy()?


Solution

  • Is there any better way with Collectors.groupingBy()?

    You need the flavor of groupingBy(classifier,mapFactory,downstream) which allows to specify a mapFactory.

    As a downstream collector, we need to apply combination of collectingAndThen() and counting() because the resulting type produced by counting() is Long and we need to turn it into Integer.

    Map<String, Integer> fooNameByCount = fooList.stream()
        .sorted(Comparator.comparing(Foo::getNumber))
        .collect(Collectors.groupingBy(
            Foo::getName,
            LinkedHashMap::new,
            Collectors.collectingAndThen(Collectors.counting(),
                Long::intValue)
        ));
    

    We can achieve the same result by using a single collector with the following version of toMap(keyMapper,valueMapper,mergeFunction,mapFactory):

    Map<String, Integer> fooNameByCount = fooList.stream()
        .sorted(Comparator.comparing(Foo::getNumber))
        .collect(Collectors.toMap(
            Foo::getName,
            foo -> 1,
            Integer::sum,
            LinkedHashMap::new
        ));
    

    Also note that using collector is always a preferred way to accumulate the result.

    Usage of forEach() such cases when you generate the result by the means of collect(), reduce(), etc. is discouraged by the API documentation