我们经常有这样的一些需求,将一组数据按照某个或某几个属性进行分组,属性相同的元素存储在一个List中,属性不同的元素存储在不同的List中。
- Map
> dishesByType = menu.stream() - .collect(groupingBy(Dish::getType));
通过groupingBy()方法,可以根据给定的属性进行分组,得到一个Map,Map的Key就是唯一的Type,Map的values就是Type相同的所有Dish的List。
我们把Dish::getType称为分类方法 / 分组方法。
在某些场景下,函数引用并不能得到提供,因此我们可以自定义分组方法:
- public enum CaloricLevel {
- DIET,
- NORMAL,
- FAT
- }
首先,我们定义了一个Enum,用于表示不同的组。
- Map
> group = menu.stream() - .collect(groupingBy(dish -> {
- if (dish.getCalories() <= 400)
- return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700)
- return CaloricLevel.NORMAL;
- else
- return CaloricLevel.FAT;
- }));
然后根据不同的规则,返回了不同的枚举值,这样就可以按照自己的意愿进行分组了。
我们也会遇到类似的场景:先按A分组,在内部按B分组。
- Map
>> dishesByTypeCaloricLevel = - menu.stream()
- .collect(groupingBy(Dish::getType, groupingBy(dish -> {
- if (dish.getCalories() <= 400)
- return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700)
- return CaloricLevel.NORMAL;
- else
- return CaloricLevel.FAT;
- })
- ));
很好理解,就是嵌套使用,不做过多解释。
1. 先分组,再统计个数
- Map
typesCount = menu.stream() - .collect(groupingBy(Dish::getType, counting()));
groupBy()有两个入参,第一个是分组条件,第二个是统计操作,这里是counting()进行计数。
2. 先分组,再取最大值
- Map
> mostCaloricByType = menu.stream() - .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));
这里是maxBy()求算最大值。
3. 由于取最大值可能不存在,我们可以使用collectingAndThen方法,进行后续取值操作Optional::get
- Map
mostCaloricByType = menu.stream() - .collect(groupingBy(Dish::getType,
- collectingAndThen(maxBy(comparingInt(Dish::getCalories)),
- Optional::get)));
4. 先分组,再求和
- Map
totalCaloriesByType = - menu.stream().collect(groupingBy(Dish::getType,
- summingInt(Dish::getCalories)));
先按照type进行分组,内部使用summingInt求和计算。
5. 先分组,再映射
- Map
> result = menu.stream() - .collect(groupingBy(Dish::getType,
- mapping(dish -> {
- if (dish.getCalories() <= 400)
- return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700)
- return CaloricLevel.NORMAL;
- else
- return CaloricLevel.FAT;
- }, toSet())));
这里进行了几层嵌套:
collect
groupingBy
mapping
rule, toSet
最后的toSet是没法保证是用什么实现的,HashSet还是其他,我们可以使用toCollection(HashSet::new)指定
- Map
> result = menu.stream() - .collect(groupingBy(Dish::getType,
- mapping(dish -> {
- if (dish.getCalories() <= 400)
- return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700)
- return CaloricLevel.NORMAL;
- else
- return CaloricLevel.FAT;
- }, toCollection(HashSet::new)
- )));