Collectors.groupingBy 分组统计详解
1 List 转 Map1.1 使用 groupingBy 分组根据部门分组按照自定义Key分组多级分组根据部门分组,求ID的List根据部门分组,Count人数根据部门分组,求Sex的Set根据部门分组,求Sex的去重个数 1.2 使用 partitioningBy 分区1.3 使用 toMapList 转 Map<ID, User>List 转 Map<ID, Name> 2 求最大值、最小值、平均值、总和2.1 不分组,直接统计2.2 先分组,再统计求各个部门中,Age最大的人求各个部门中,Age的最大值求各个部门中,Age的平均值求各个部门中,Age的总和使用 IntSummaryStatistics 统计 3 BigDecimal 类型处理3.1 不分组,直接统计3.2 先分组,再统计求各个部门,最大的Salary求各个部门,最小的Salary求各个部门,Salary总和求各个部门,Salary平均值工作中能够熟练使用Collectors中groupingBy、reducing、toMap非常重要,因为这些巧妙的函数可以大大提高开发效率,所以学习好它们刻不容缓。先准备好一个List集合供测试用。
public static List<User> getUserList() {List<User> users = new ArrayList<>();users.add(new User("1", "name1", "Java组", 33, "男", new BigDecimal("25000"), true));users.add(new User("2", "name2", "Java组", 31, "女", new BigDecimal("28000"), true));users.add(new User("3", "name3", "前端组", 33, "男", new BigDecimal("18000"), true));users.add(new User("4", "name4", "前端组", 25, "男", new BigDecimal("19000"), false));users.add(new User("5", "name5", "QA组", 24, "女", new BigDecimal("15000"), true));users.add(new User("6", "name6", "产品组", 34, "女", new BigDecimal("12000"), true));return users;}
1 List 转 Map
1.1 使用 groupingBy 分组
根据部门分组
Map<String, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getDept));
按照自定义Key分组
Map<String, List<User>> groupByDeptAppendName = users.stream().collect(Collectors.groupingBy(user -> user.getDept() + "&" + user.getName()));
多级分组
Map<String, Map<String, List<User>>> groupByDeptAndGender = users.stream().filter(user -> Objects.nonNull(user.getSex())) // group by 的字段不能有null值.collect(Collectors.groupingBy(User::getDept,Collectors.groupingBy(User::getSex)));
根据部门分组,求ID的List
Map<String, List<String>> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.mapping(User::getId,Collectors.toList())));
{Java组=[1, 2], QA组=[5], 前端组=[3, 4], 产品组=[6]}
根据部门分组,Count人数
Map<String, Long> groupBuyDeptThenCount = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.counting()));
根据部门分组,求Sex的Set
Map<String, Set<String>> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.mapping(User::getSex,Collectors.toSet())));
{Java组=[女, 男], QA组=[女], 前端组=[男], 产品组=[女]}
根据部门分组,求Sex的去重个数
Collectors.collectingAndThen()
它接受两个参数:downstream
和finisher
。其中
downstream
是一个Collector收集器,用于对数据流中的元素进行收集操作;finisher
是一个Function函数,用于对downstream
的收集结果进行处理,并返回最终的结果。
Map<String, Integer> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.collectingAndThen(Collectors.mapping(User::getSex,Collectors.toSet()),a -> a.size())));
{Java组=2, QA组=1, 前端组=1, 产品组=1}
1.2 使用 partitioningBy 分区
Map<Boolean, List<User>> collect = users.stream().collect(Collectors.partitioningBy(a -> a.getAge() > 30));
1.3 使用 toMap
List 转 Map<ID, User>
Map<String, User> userMap = users.stream().collect(Collectors.toMap(User::getId,Function.identity(),(k1, k2) -> k1 //key重复,用第一个));
List 转 Map<ID, Name>
Map<String, String> idToName = users.stream().collect(Collectors.toMap(User::getId,User::getName));
2 求最大值、最小值、平均值、总和
2.1 不分组,直接统计
求年龄最大的人:Optional<User> maxAgeUserOptional = users.stream().collect(Collectors.maxBy(paring(User::getAge)));求年龄最小的人:Optional<User> minAgeUserOptional = users.stream().collect(Collectors.minBy(paring(User::getAge)));求最大的年龄:int maxAge = users.stream().mapToInt(User::getAge).max().getAsInt();求最小的年龄:int minAge = users.stream().mapToInt(User::getAge).min().getAsInt();求年龄总和:int sumAge = users.stream().mapToInt(User::getAge).sum();求平均年龄:double avgAge = users.stream().mapToInt(User::getAge).average().getAsDouble();
2.2 先分组,再统计
求各个部门中,Age最大的人
Map<String, User> groupByDeptThenGetMaxAgeUser = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.collectingAndThen(Collectors.maxBy(paring(User::getAge,Comparator.nullsLast(Integer::compareTo))),Optional::get)));
求各个部门中,Age的最大值
Map<String, Integer> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.collectingAndThen(Collectors.maxBy(paringInt(User::getAge)),a -> a.isPresent() ? a.get().getAge() : null)));
求各个部门中,Age的平均值
Map<String, Double> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.averagingInt(User::getAge)));
求各个部门中,Age的总和
Map<String, Integer> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.summingInt(User::getAge)));
使用 IntSummaryStatistics 统计
Map<String, IntSummaryStatistics> collect = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.summarizingInt(User::getAge)));for (Map.Entry<String, IntSummaryStatistics> entry : collect.entrySet()) {IntSummaryStatistics summaryStatistics = entry.getValue();System.out.println("----------------key----------------" + entry.getKey());System.out.println("求和:" + summaryStatistics.getSum());System.out.println("求平均" + summaryStatistics.getAverage());System.out.println("求最大:" + summaryStatistics.getMax());System.out.println("求最小:" + summaryStatistics.getMin());System.out.println("求总数:" + summaryStatistics.getCount());}
3 BigDecimal 类型处理
3.1 不分组,直接统计
List<BigDecimal> userSalary = users.stream().map(User::getSalary).collect(Collectors.toList());Optional<BigDecimal> maxSalary = userSalary.stream().reduce(BigDecimal::max);Optional<BigDecimal> minSalary = userSalary.stream().reduce(BigDecimal::min);BigDecimal sumSalary = userSalary.stream().reduce(BigDecimal.ZERO, BigDecimal::add);BigDecimal avgSalary= sumSalary.divide(BigDecimal.valueOf(userSalary.size()), 2, BigDecimal.ROUND_HALF_UP);
3.2 先分组,再统计
求各个部门,最大的Salary
Map<String, BigDecimal> groupByDeptThenGetMaxSalary = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.reducing(BigDecimal.ZERO,User::getSalary,BigDecimal::max)));
求各个部门,最小的Salary
Map<String, BigDecimal> groupByDeptThenGetMinSalary = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.reducing(BigDecimal.valueOf(Long.MAX_VALUE),User::getSalary,BigDecimal::min)));如果考虑Salary有null值,可以如下处理Map<String, BigDecimal> groupByDeptThenGetMinSalary = users.stream().collect(Collectors.groupingBy(User::getDept,Collectors.collectingAndThen(Collectors.reducing((c1, c2) -> c1.getSalary().compareTo(c2.getSalary()) > 0 ? c2 : c1),a -> a.isPresent() ? a.get().getSalary() : null)));
求各个部门,Salary总和
Map<String, BigDecimal> groupByDeptThenGetSumSalary = users.stream().filter(user -> Objects.nonNull(user.getDept())) //比较的字段不能有null值.collect(Collectors.groupingBy(User::getDept,Collectors.reducing(BigDecimal.ZERO,User::getSalary,BigDecimal::add)));
求各个部门,Salary平均值
Collectors
中有averagingInt、averagingLong、averagingDouble
等,但是没有averagingBigDecimal
。
参考java lambada 对list进行分组汇总,实现自定义averagingBigDecimal
Map<String, BigDecimal> groupByDeptThenGetAvgSalary = users.stream().filter(user -> Objects.nonNull(user.getDept())) //比较的字段不能有null值.collect(Collectors.groupingBy(User::getDept,CustomCollectors.averagingBigDecimal(User::getSalary, 2, 2)));