With the classes:
public class Person {
private String name;
private Color favouriteColor;
}
public enum Color {GREEN, YELLOW, BLUE, RED, ORANGE, PURPLE}
Having a List<Person>
using the Java8 Stream API can I trasform it in a Map<Color, Long>
having the count of each Color
, also for the color that aren't included in the list.
Example:
List<Person> list = List.of(
new Person("Karl", Color.RED),
new Person("Greg", Color.BLUE),
new Person("Andrew", Color.GREEN)
);
Trasforming this list in a Map with all the colors of the enum with their count.
Thanks
SOLVED
Solved using a custom collector:
public static <T extends Enum<T>> Collector<T, ?, Map<T, Long>> counting(Class<T> type) {
return Collectors.toMap(
Function.<T>identity(),
x -> 1l,
Long::sum,
() -> new HashMap(Stream.of(type.getEnumConstants()).collect(Collectors.toMap(Function.<T>identity(),t -> 0l)))
);
}
list.stream()
.map(Person::getFavouriteColor)
.collect(counting(Color.class))
You can use the groupingBy
collector to create a map but if you want to add default values for absent keys, you have to ensure that the returned map is mutable by providing a Supplier
for the map. On the other hand, this adds the opportunity to create an EnumMap
which is more suitable to this use case:
EnumMap<Color, Long> map = list.stream().collect(Collectors.groupingBy(
Person::getFavouriteColor, ()->new EnumMap<>(Color.class), Collectors.counting()));
EnumSet.allOf(Color.class).forEach(c->map.putIfAbsent(c, 0L));
May be, you think it’s cleaner to fill the map with default values within the supplier function:
EnumMap<Color, Long> map = list.stream().collect(Collectors.toMap(
Person::getFavouriteColor, x->1L, Long::sum, ()->{
EnumMap<Color, Long> em = new EnumMap<>(Color.class);
EnumSet.allOf(Color.class).forEach(c->em.put(c, 0L));
return em;
}));
but of course, you can also use a stream to create that initial map:
EnumMap<Color, Long> map = list.stream().collect(Collectors.toMap(
Person::getFavouriteColor, x->1L, Long::sum, () ->
EnumSet.allOf(Color.class).stream().collect(Collectors.toMap(
x->x, x->0L, Long::sum, ()->new EnumMap<>(Color.class)))));
But for completeness, you can do the same without the stream API, if you wish:
EnumMap<Color, Long> map = new EnumMap<>(Color.class);
list.forEach(p->map.merge(p.getFavouriteColor(), 1L, Long::sum));
EnumSet.allOf(Color.class).forEach(c->map.putIfAbsent(c, 0L));
You can try with:
Map<Color, Long> counted = list.stream()
.collect(Collectors.groupingBy(Person::getFavouriteColor(),
Collectors.counting()));
Of course, this implies that you have a getter for the Person#favouriteColor
member.
Then, in order to add the non-existing Color
s to the Map, you can stream over all Color
values, filter those who are not used as keys for the Map yet and put them with value of 0
:
Stream.of(Color.values())
.filter(x -> !counted.containsKey(x))
.forEach(x -> counted.put(x, 0L));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With