简体   繁体   English

Java8流分组通过枚举和计数

[英]Java8 stream groupingBy enum and counting

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. 使用Java8 Stream API获得List<Person>可以在Map<Color, Long> ,其中包含每种Color的计数,以及未包含在列表中的颜色。 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. 您可以使用groupingBy收集器创建映射,但是如果要为缺失键添加默认值,则必须通过为映射提供Supplier来确保返回的映射是可变的。 On the other hand, this adds the opportunity to create an EnumMap which is more suitable to this use case: 另一方面,这增加了创建更适合此用例的EnumMap的机会:

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: 但是为了完整性,如果您愿意,可以在没有流API的情况下执行相同的操作:

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. 当然,这意味着你有一个Person#favouriteColor成员的getter。

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 : 然后,为了将不存在的Color添加到Map,您可以流式传输所有Color值,过滤那些尚未用作Map的键的值,并将它们的值设置为0

Stream.of(Color.values())
      .filter(x -> !counted.containsKey(x))
      .forEach(x -> counted.put(x, 0L));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM