Java8 Stream(12)Collectors.groupingBy 分组统计详解

  • Post author:
  • Post category:java


工作中能够熟练使用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(Comparator.comparing(User::getAge))
);

求年龄最小的人:
Optional<User> minAgeUserOptional = users.stream().collect(
        Collectors.minBy(Comparator.comparing(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(
                                Comparator.comparing(
                                        User::getAge,
                                        Comparator.nullsLast(Integer::compareTo)
                                )
                        ),
                        Optional::get
                )
        )
);



求各个部门中,Age的最大值

Map<String, Integer> collect = users.stream().collect(
        Collectors.groupingBy(
                User::getDept,
                Collectors.collectingAndThen(
                        Collectors.maxBy(
                                Comparator.comparingInt(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
                )
        )
);

如果考虑Salarynull值,可以如下处理
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)
                )
        );



版权声明:本文为winterking3原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。