groupingBy() 可按部门分组计数,返回 Map;null 部门默认归入 null 键,可 filter 跳过或用 Optional 转为 "Unknown";需多维统计时宜用 summarizingDouble;并发场景需显式指定 ConcurrentHashMap::new;语义一致性比语法正确更重要。

groupingBy() 基础用法:按部门字段分组计数
直接用 Collectors.groupingBy() 配合 Collectors.counting() 即可完成统计,无需手动遍历或构建 Map。关键在于传入的分类函数(如 e -> e.getDepartment())必须返回可比较、非 null 的键值。
- 员工类需有
getDepartment()方法,且返回值不能为null,否则抛NullPointerException - 部门名若含空格或特殊字符,不影响分组,但会影响后续打印或 JSON 序列化时的可读性
- 返回类型是
Map<string long></string>,不是Map<string integer></string>——counting()固定返回Long
Map<String, Long> deptCount = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()));
遇到 null 部门值怎么办
如果部分员工 department 为 null,默认分组会把它们归入 null 键,但某些场景下你可能希望跳过或统一归到 "Unknown" 下。
- 跳过 null:在流中先
filter(e -> e.getDepartment() != null) - 归为默认值:改用
Collectors.groupingBy(e -> Optional.ofNullable(e.getDepartment()).orElse("Unknown"), Collectors.counting()) - 注意:
Map的 key 允许为null,但若后续传给 Spring REST 接口或 Jackson,默认会忽略null键,导致数据丢失
想同时统计人数和平均薪资?用 groupingBy + summarizingDouble
单纯计数太单薄,实际常需多维度聚合。这时不要嵌套多个 groupingBy(),而是用 Collectors.summarizingDouble() 或自定义 Collector。
-
summarizingDouble返回DoubleSummaryStatistics,含 count / sum / average / min / max - 若只要总人数和总薪资,用
Collectors.summingDouble(Employee::getSalary)更轻量 - 避免写成
groupingBy(..., groupingBy(...))—— 这会生成嵌套 Map,性能差且难维护
Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summarizingDouble(Employee::getSalary)
));
性能与并发:大数据量时别踩这个坑
默认 groupingBy() 是顺序执行,返回 HashMap。若数据量超 10 万且对性能敏感,要注意两点:
- 不加
ConcurrentHashMap::new时,即使 stream 是 parallel,结果 Map 仍是非线程安全的 —— 并行流不会自动帮你换成并发容器 - 真要并发收集,必须显式指定下游 map factory:
Collectors.groupingBy(Employee::getDepartment, ConcurrentHashMap::new, Collectors.counting()) - 但注意:并发版在小数据集上反而更慢,JVM 预热和分段锁开销明显,建议 >50 万记录再考虑

















