Java Stream API 多级分组通过嵌套 Collectors.groupingBy 实现,支持两级(如按原机房再按目标机房)、三级及以上(如部门→年龄段→性别)分组,可混合 summingDouble、averagingInt 等下游收集器,并推荐用 record 或枚举定义类型安全的复合键。

Java Stream API 实现多级分组,核心在于嵌套使用 Collectors.groupingBy,而不是写多层循环或手动构建 Map。它天然支持“先按 A 分,再在每组内按 B 分”,结构清晰、代码简洁、线程安全(配合不可变数据时)。
两级分组:最常用也最直观
比如订单数据要先按“原机房”分,再在每个机房里按“目标机房”统计数量:
- 外层 groupingBy 提取一级键(如
Order::getOriginalRoom) - 内层作为下游 collector,继续 groupingBy 或直接聚合(如
Collectors.counting()) - 返回类型自动推导为
Map<string map long>></string>
示例代码:
public Map<String, Map<String, Long>> countOrdersByMultiLevelRoom(List<Order> orders) {
return orders.stream()
.collect(Collectors.groupingBy(
Order::getOriginalRoom,
Collectors.groupingBy(Order::getTargetRoom, Collectors.counting())
));
}
三级及以上分组:靠连续嵌套
三级分组只需再套一层 groupingBy。例如:按部门 → 年龄段 → 性别 统计人数:
立即学习“Java免费学习笔记(深入)”;
- 第一级:部门(
Person::getDepartment) - 第二级:年龄段(自定义函数,如
p -> p.getAge() < 30 ? "青年" : "中年") - 第三级:性别(
Person::getGender),下游用Collectors.counting() - 返回类型是
Map<String, Map<String, Map<String, Long>>>
注意:嵌套越深,可读性越低,建议封装成独立方法或使用 record 类简化键结构。
混合聚合:分组后不只存 List,还能算总数/平均值/最大值
多级分组常搭配其他 downstream collector,避免先分组再遍历计算:
- 用
Collectors.summingDouble(Person::getSalary)直接算各组薪资总和 - 用
Collectors.averagingInt(Person::getAge)算平均年龄 - 用
Collectors.maxBy(Comparator.comparing(Person::getSalary))找出每组最高薪员工 - 甚至组合多个统计:用
Collectors.collectingAndThen(..., Optional::get)解包结果
自定义分组键提升表达力
硬编码字符串(如 "青年"、"高价")易出错且难维护。推荐做法:
- 定义枚举或 record 表示复合键,例如
record DeptAgeGroup(String dept, String ageRange) { } - 在一级 groupingBy 中用 lambda 构造该 record,让键语义明确、类型安全
- 这样不仅利于调试,也方便后续做 equals/hashCode 比较或序列化


















