groupingBy() 是 Stream API 里最常用的收集器之一,这篇真实从最基础的分组一路验证到多级分组、downstream collector 组合,最后确认它返回的到底是什么类型的 Map、能不能继续修改。
上一篇:Optional.orElse() vs orElseGet() 真实验证
用一批员工数据,验证最基础的分组,以及配合counting()/averagingInt()/mapping()各自的真实效果。
StreamGroupingByDemo.java employees.stream().collect(Collectors.groupingBy(Employee::dept)); employees.stream().collect(Collectors.groupingBy(Employee::dept, Collectors.counting())); employees.stream().collect(Collectors.groupingBy(Employee::dept, Collectors.averagingInt(Employee::salary))); employees.stream().collect(Collectors.groupingBy(Employee::dept, Collectors.mapping(Employee::name, Collectors.toList())));
真实分组结果的key集合: [hr, engineering, sales]
engineering组真实人数: 3
真实各部门人数统计: {hr=1, engineering=3, sales=2}
真实各部门平均薪资: {hr=5000.0, engineering=9666.666666666666, sales=6500.0}
真实各部门姓名列表: {hr=[Frank], engineering=[Alice, Bob, Carl], sales=[Dave, Eve]}
真实结果验证了 groupingBy() 的分层设计:最基础的形式真实返回 Map<K, List<T>>,每组是原始元素的完整列表;加上第二个参数(downstream collector)后,真实能在分组的同时对每组内部再做一次归约——counting() 真实把每组变成元素个数,averagingInt() 真实计算每组的平均值,mapping() 真实先对每个元素做一次转换(取名字)再收集成列表。这种"分组 + downstream"的组合方式是 Collectors 工具类里最灵活的部分。
groupingBy有一个三参数重载,可以指定Map的具体实现类型。
StreamGroupingByDemo.java Collectors.groupingBy(Employee::dept, Collectors.counting()); // 默认Map类型 Collectors.groupingBy(Employee::dept, TreeMap::new, Collectors.counting()); // 指定TreeMap
真实用TreeMap工厂得到按key排序的结果: {engineering=3, hr=1, sales=2}
groupingBy默认返回的Map真实类型: HashMap
指定TreeMap工厂后真实返回的Map类型: TreeMap
最后验证嵌套分组(先按部门再按薪资档位),以及分组结果本身是否可以继续修改。
真实engineering组内薪资>=8000的人数: 3 groupingBy返回的Map真实是可变的,put()真实成功: true
真实结果证明 groupingBy(Employee::dept, Collectors.groupingBy(...)) 这种嵌套写法真实能构建出 Map<String, Map<Boolean, List<Employee>>> 这样的多级分组结构。另外一个容易被忽视的细节是:groupingBy() 返回的 Map 真实是可变的(内部实现基于 HashMap.merge()/computeIfAbsent() 完成可变归约),不像系列前面 List.of()/Collectors.toUnmodifiableList()那样返回不可变集合——这意味着拿到分组结果后,调用方真实可以继续对它做增删改,如果不希望被后续代码意外修改,需要自己再包一层 Collections.unmodifiableMap()。
每一行都是真实运行这段代码得到的结果。