Collectors.groupingBy 多级分组需嵌套调用,每级传入对应分类函数;一级按字段分组得 Map<K, List<V>>,二级嵌套得 Map<K1, Map<K2, List<V>>>,可混用 counting、averagingDouble 等下游收集器;注意处理 null 值避免 NPE。

用 Collectors.groupingBy 对复杂对象集合做多级分组,核心在于嵌套调用,每级传入对应的分类函数,配合 Collectors.groupingBy 或其他下游收集器(如 Collectors.toList、Collectors.counting)完成层级构建。
一级分组:按单个字段归类
最基础的用法是按对象的某个属性分组,结果为 Map<k list>></k>:
- 例如对
List<Person>按城市分组:
Map<String, List<Person>> byCity = persons.stream()<br> .collect(Collectors.groupingBy(Person::getCity)); - 注意:若某城市无对应人员,该 key 不会出现在 map 中;key 为
null时默认被忽略(除非显式配置Collectors.groupingBy(..., HashMap::new)并允许 null key)。
二级分组:嵌套 groupingBy 实现两层结构
在一级分组的下游收集器中再次使用 groupingBy,即可实现“城市 → 性别 → 人员列表”这类两级结构:
- 写法示例:
Map<String, Map<String, List<Person>>> byCityAndGender = persons.stream()<br> .collect(Collectors.groupingBy(Person::getCity,<br> Collectors.groupingBy(Person::getGender))); - 返回类型是
Map<String, Map<String, List<Person>>>,外层 key 是城市,内层 key 是性别,值是对应人员列表。 - 可自由组合下游收集器:比如第二级用
Collectors.counting()得到各城市下各性别人数,类型变为Map<String, Map<String, Long>>。
多级分组 + 自定义下游处理
超过两级时继续嵌套,同时可混用其他收集器增强表达力:
- 三级分组(城市 → 性别 → 年龄段 → 人数):
Map<String, Map<String, Map<String, Long>>> result = persons.stream()<br> .collect(Collectors.groupingBy(Person::getCity,<br> Collectors.groupingBy(Person::getGender,<br> Collectors.groupingBy(p -> p.getAge() < 30 ? "青年" : "非青年",<br> Collectors.counting())))); - 若需对每组做聚合(如平均年龄),第二级可用
Collectors.averagingDouble(Person::getAge),类型变为Map<String, Map<String, Double>>。 - 注意嵌套顺序与最终 map 层级一致:最外层
groupingBy对应最外层 key,越内层的groupingBy越靠近 value 端。
避免空指针与空集合问题
实际数据中常有字段为 null,直接方法引用会抛 NullPointerException:
- 安全写法:用 lambda 包装判空逻辑,例如
p -> p.getCity() != null ? p.getCity() : "未知"; - 若希望跳过 null 值,可在 stream 中先
filter(Objects::nonNull)或对字段单独过滤; - 空集合不会导致异常,但分组结果中不会包含空组 —— 若业务要求保留所有可能 key(如所有城市名),需额外补全 map。

















