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分以下的放到不及格组......' 这样我们就有了一个分组清单,还可以数一数每组有多少人,甚至把他们的名字拼起来打印。"

相关推荐
oak隔壁找我1 天前
JVM常用调优参数
java·后端
蝎子莱莱爱打怪1 天前
OpenClaw 从零配置指南:接入飞书 + 常用命令 + 原理图解
java·后端·ai编程
狼爷1 天前
Go 没有 override?别硬套继承!用接口+嵌入,写更清爽的“覆盖”逻辑
java·go
小兔崽子去哪了1 天前
Java 自动化部署
java·后端
ma_king1 天前
入门 java 和 数据库
java·数据库·后端
后端AI实验室1 天前
我用Cursor开发了3个月,整理出这套提效4倍的工作流
java·ai
码路飞2 天前
GPT-5.3 Instant 终于学会好好说话了,顺手对比了下同天发布的 Gemini 3.1 Flash-Lite
java·javascript
SimonKing2 天前
OpenCode AI编程助手如何添加Skills,优化项目!
java·后端·程序员
Seven972 天前
剑指offer-80、⼆叉树中和为某⼀值的路径(二)
java
怒放吧德德2 天前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty