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()
?
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