collectors.groupingby 的核心是基于函数式归约的惰性构建过程,不手动维护 map,而是在 collect 阶段一次性完成键提取、分组归集与结果组装;分类函数纯函数化生成键,下游收集器决定分组内容形态,支持嵌套分组与并发安全,map 创建与填充延迟至 collect 调用时。

Collectors.groupingBy 的核心不是“遍历+if判断”,而是基于函数式归约(reduction)的惰性构建过程。它不手动维护 Map,也不在流处理中途反复 put,而是在 collect 阶段一次性完成键提取、分组归集与结果组装。
分类函数驱动键生成
传入的 classifier(如 Employee::getCity)会被应用于每个流元素,生成一个分组键(K)。这个键不需唯一预定义,也不要求实现 Comparable 或 hashCode 特殊逻辑——只要对象能正确参与 HashMap 的 key 判等即可。
- 每次调用 classifier 都是纯函数行为:输入元素 → 输出键,无副作用
- 键类型 K 由 classifier 的返回类型决定,编译期即确定,保障类型安全
- 若键为 null,会抛出 NullPointerException(除非显式使用 null-tolerant 分类逻辑)
下游收集器决定分组内容形态
groupingBy 默认使用 toList() 作为下游收集器,这意味着每个键对应一个 ArrayList。但你可以替换为任意 Collector,比如 counting()、summingInt()、toSet(),甚至嵌套另一个 groupingBy。
- 下游收集器独立运行:对每个分组内的元素子流单独执行其归约逻辑
- 例如 groupingBy(Employee::getCity, Collectors.counting()) 中,counting() 不构造 List,只累加 long 计数
- 多级分组本质是“下游收集器为另一个 groupingBy”,形成 Map
>> 结构
并发安全与线程模型
groupingBy 本身不是线程安全的收集器;它默认适用于顺序流(sequential stream)。若用于并行流(parallelStream),底层会依赖 ConcurrentHashMap 和 fork-join 拆分策略,但要求:
- classifier 函数必须是无状态且线程安全的
- 下游收集器也需支持并发(如 toList()、toSet()、counting() 均满足)
- 不建议在 parallelStream 中使用自定义 mutable 容器或非并发收集器
Map 实例的创建与填充时机
结果 Map 并非预先分配,也不是边流边 put。collect 方法内部通过 Supplier
- 整个过程封装在 Collector 接口的 supplier/accumulator/combiner 三元组中
- 你写的 groupingBy(...) 只是声明意图,真正执行发生在 .collect() 调用那一刻
- 没有中间 List 存储,也没有显式循环——这是 Stream 管道化执行的本质
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











