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


工作中能够熟练使用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() 它接受两个参数:downstreamfinisher。其中

  • 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)
                )
        );
  • 7
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java 8中,使用Stream流的Collectors.groupingBy方法可以对数据进行分组操作。但是需要注意的是,如果分组的字段数据存在丢失的情况,可能是由于以下原因导致的: 1. 分组字段的数据类型不一致:如果分组字段的数据类型不一致,可能会导致分组时数据丢失。例如,如果分组字段是一个对象的属性,而该属性的数据类型在不同的对象中不一致,那么在分组时可能会导致某些数据被丢弃。 2. 分组字段的hashCode和equals方法未正确重写:在进行分组操作时,需要使用分组字段的hashCode和equals方法来确定分组的依据。如果这两个方法未正确重写,可能会导致分组时数据丢失。 为了避免数据丢失的情况发生,可以采取以下措施: 1. 确保分组字段的数据类型一致:在进行分组操作之前,可以先对分组字段的数据类型进行统一,保它们具有相同的数据类型。 2. 重写分组字段的hashCode和equals方法:如果分组字段是一个自定义对象的属性,需要确保该属性的hashCode和equals方法已正确重写,以确保分组操作的准确性。 下面是一个示例代码,演示了如何使用Java 8的Stream流和Collectors.groupingBy方法进行分组操作: ```java import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class GroupingByDemo { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eva"); // 按照名字的首字母进行分组 Map<Character, List<String>> groups = names.stream() .collect(Collectors.groupingBy(name -> name.charAt(0))); // 输出分组结果 groups.forEach((key, value) -> System.out.println(key + ": " + value)); } } ``` 运行以上代码,将会按照名字的首字母进行分组,并输出分组结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑟王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值