315. Java Stream API - 使用 Collectors.groupingBy() 对流元素分组 —— 创建分组映射和直方图的利器

文章目录

  • [315. Java Stream API - 使用 `Collectors.groupingBy()` 对流元素分组 ------ 创建分组映射和直方图的利器](#315. Java Stream API - 使用 Collectors.groupingBy() 对流元素分组 —— 创建分组映射和直方图的利器)
    • [✅ 基本用法:将流元素按分类器函数分组](#✅ 基本用法:将流元素按分类器函数分组)
      • [🧠 什么是分类器?](#🧠 什么是分类器?)
      • [📌 基本示例:按字符串长度分组](#📌 基本示例:按字符串长度分组)
      • [💡 输出结果:](#💡 输出结果:)
    • [✅ 进阶用法一:使用下游收集器 `counting()` 统计每组元素数量](#✅ 进阶用法一:使用下游收集器 counting() 统计每组元素数量)
      • [💡 输出结果:](#💡 输出结果:)
    • [✅ 进阶用法二:使用下游收集器 `joining()` 拼接每组字符串](#✅ 进阶用法二:使用下游收集器 joining() 拼接每组字符串)
      • [💡 输出结果:](#💡 输出结果:)
    • [✅ 高阶用法:自定义 Map 类型(第三个参数)](#✅ 高阶用法:自定义 Map 类型(第三个参数))
      • [💡 输出结果(按 key 排序):](#💡 输出结果(按 key 排序):)
    • [🧠 小结回顾](#🧠 小结回顾)
    • [🚀 实际应用场景](#🚀 实际应用场景)

315. Java Stream API - 使用 Collectors.groupingBy() 对流元素分组 ------ 创建分组映射和直方图的利器

在 Java 的 Stream API 中,Collectors.groupingBy() 是一个非常核心的收集器,用于将流中的元素按照某种规则进行分组,并构建一个 Map。你可以利用它创建直方图(Histogram)、分组统计表、分组连接字符串等,灵活性非常强。


✅ 基本用法:将流元素按分类器函数分组

🧠 什么是分类器?

groupingBy() 的第一个参数是一个分类器(classifier)函数 ,它定义了如何为每个流元素生成分组的键(key)。这个键可以是任意类型(不能为 null),只要能对元素进行合理的分类。

📌 基本示例:按字符串长度分组

java 复制代码
Collection<String> strings = List.of("one", "two", "three", "four", "five", "six", 
                                     "seven", "eight", "nine", "ten", "eleven", "twelve");

Map<Integer, List<String>> map = strings.stream()
    .collect(Collectors.groupingBy(String::length));

map.forEach((key, value) -> System.out.println(key + " :: " + value));

💡 输出结果:

java 复制代码
3 :: [one, two, six, ten]
4 :: [four, five, nine]
5 :: [three, seven, eight]
6 :: [eleven, twelve]

🧩 String::length 就是分类器,它把每个字符串按长度分组。结果是一个 Map<Integer, List<String>>,键为长度,值为对应长度的字符串列表。


✅ 进阶用法一:使用下游收集器 counting() 统计每组元素数量

你可以为 groupingBy() 提供一个下游收集器(downstream collector) ,用于进一步处理每个分组的值。比如统计每组中元素的个数,就可以使用 Collectors.counting()

java 复制代码
Map<Integer, Long> map = strings.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.counting()
    ));

map.forEach((key, value) -> System.out.println(key + " :: " + value));

💡 输出结果:

java 复制代码
3 :: 4
4 :: 3
5 :: 3
6 :: 2

📊 这就构成了一个按字符串长度统计数量的直方图(Histogram)!非常适用于词频统计、日志分析等场景。


✅ 进阶用法二:使用下游收集器 joining() 拼接每组字符串

如果你希望每个分组的字符串不是以列表的形式展示,而是以逗号分隔的字符串形式呈现,可以使用 Collectors.joining()

java 复制代码
Map<Integer, String> map = strings.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.joining(", ")
    ));

map.forEach((key, value) -> System.out.println(key + " :: " + value));

💡 输出结果:

java 复制代码
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve

📎 非常适合用于打印清晰的报告或日志输出。


✅ 高阶用法:自定义 Map 类型(第三个参数)

有时候你可能不希望使用默认的 HashMap,而想用 TreeMap(按键排序)或 LinkedHashMap(保持插入顺序)。这时候可以使用 groupingBy() 的第三个重载版本,手动传入一个 Map 工厂(Supplier<Map>):

java 复制代码
Map<Integer, List<String>> map = strings.stream()
    .collect(Collectors.groupingBy(
        String::length,
        TreeMap::new, // 指定用 TreeMap
        Collectors.toList()
    ));

map.forEach((key, value) -> System.out.println(key + " :: " + value));

💡 输出结果(按 key 排序):

java 复制代码
3 :: [one, two, six, ten]
4 :: [four, five, nine]
5 :: [three, seven, eight]
6 :: [eleven, twelve]

📦 注意:默认 groupingBy() 使用的是 HashMap,如果你对顺序有要求,一定要使用此重载!


🧠 小结回顾

用法 示例 返回类型
基本分组 .groupingBy(String::length) Map<Integer, List<String>>
分组计数 .groupingBy(String::length, counting()) Map<Integer, Long>
分组连接 .groupingBy(String::length, joining(", ")) Map<Integer, String>
自定义Map类型 .groupingBy(String::length, TreeMap::new, toList()) TreeMap<Integer, List<String>>

🚀 实际应用场景

  • 📊 构建分类直方图(如词长统计、订单状态分类)
  • 📋 按属性分类列表(如按部门分组员工)
  • 🧾 按属性汇总信息(如每类产品的总销售额)

如果你希望在讲解中更加生动,还可以举一个现实世界的例子,比如:

"就像我们把学生按成绩分等级一样,groupingBy() 就是在说:'把所有分数在90分以上的放到优秀组,60分以下的放到不及格组......' 这样我们就有了一个分组清单,还可以数一数每组有多少人,甚至把他们的名字拼起来打印。"

相关推荐
何仙鸟10 分钟前
GarmageSet下载和处理
java·开发语言
wefly201715 分钟前
免安装!m3u8live.cn在线 M3U8 播放器,小白也能快速上手
java·开发语言·python·json·php·m3u8·m3u8在线转换
yuweiade17 分钟前
springboot和springframework版本依赖关系
java·spring boot·后端
ywf121518 分钟前
springboot设置多环境配置文件
java·spring boot·后端
小马爱打代码30 分钟前
SpringBoot + 消息生产链路追踪 + 耗时分析:从创建到发送,全链路性能可视化
java·spring boot·后端
薛先生_0991 小时前
js学习语法第一天
开发语言·javascript·学习
jessecyj1 小时前
Spring boot整合quartz方法
java·前端·spring boot
苦瓜小生1 小时前
【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind
java·前端·javascript
报错小能手1 小时前
深入理解 Linux 虚拟内存管理
开发语言·操作系统
和沐阳学逆向2 小时前
我现在怎么用 CC Switch 管中转站,顺手拿 Codex 举个例子
开发语言·javascript·ecmascript