groupingBy() 分组后获取计数应使用 Collectors.counting(),返回 Map;多字段分组推荐用 record 封装 key;key mapper 需防 null;下游可嵌套 summing/averaging 等 collector 实现链式聚合。

groupingBy() 分组后如何获取计数而非完整列表
默认 Collectors.groupingBy() 返回的是 Map,但多数统计场景只需要每组数量。直接套用 Collectors.counting() 是最轻量的解法:
- 用
Collectors.groupingBy(keyMapper, Collectors.counting()),返回Map - 避免手动遍历
List再调用size(),既冗余又低效 - 注意返回值是
Long而非Integer,若强转需自行处理溢出风险
按多个字段分组时怎么写 key
Java 8 原生不支持元组,不能直接写 groupingBy(x -> x.getA() + "-" + x.getB()) —— 这种字符串拼接 key 在含特殊字符或 null 时会崩,且无法反向解析。
- 推荐封装一个轻量
record(Java 14+):record CompositeKey(String a, Integer b) {},然后groupingBy(x -> new CompositeKey(x.getA(), x.getB())) - 若用老版本 Java,可用
AbstractMap.SimpleImmutableEntry,但可读性差、无类型安全 - 别用
Arrays.asList(a, b)当 key:List 不保证不可变,且equals/hashCode行为易引发分组错乱
分组时遇到 NullPointerException 怎么办
groupingBy() 的 key mapper 若返回 null,运行时直接抛 NullPointerException —— 这不是 bug,是设计使然,因为 HashMap 不允许 null key(除非显式用 ConcurrentHashMap 等例外)。
- 最稳妥做法:在 key mapper 中提前判空并映射到占位值,比如
x -> Optional.ofNullable(x.getCategory()).orElse("UNKNOWN") - 也可用
Collectors.groupingByConcurrent()配合自定义ConcurrentHashMap,但仅当并发流且明确接受nullkey 语义时才考虑 - 别依赖 try-catch 包裹整个 collect 操作:异常位置难定位,且掩盖了数据质量问题
想对每组做进一步聚合(如求和、平均值)怎么链式写
groupingBy() 的下游收集器(downstream collector)可嵌套任意合法 collector,这是它比手写 for 循环灵活的核心原因。
- 求和:
groupingBy(x -> x.getType(), Collectors.summingDouble(x -> x.getAmount())) - 平均值:
groupingBy(x -> x.getRegion(), Collectors.averagingInt(x -> x.getScore())) - 复合统计(同时要个数+总和):
groupingBy(x -> x.getDept(), Collectors.collectingAndThen(Collectors.toList(), list -> Map.entry(list.size(), list.stream().mapToInt(Person::getSalary).sum()))),但注意这已脱离“轻量统计”范畴,建议拆成两步或改用Collectors.summarizingInt()
嵌套层级一深就难维护,真正复杂的聚合逻辑不如先分组再对每个 value 单独处理——可读性和调试成本远低于堆 collector。










