上周学了 Stream 基础操作。今天深入 Stream 的终端操作核心——Collectors。它是将流收集为各种数据结构的工具类,掌握它才能真正发挥 Stream 的威力。
一、Collectors 是什么?
Collectors 是一个工具类,提供了大量预定义的收集器。每个收集器都是一个 Collector 接口的实现,用于将 Stream 中的元素收集到某种结果容器中。
核心分类:
| 类别 | 收集器 | 作用 |
|---|---|---|
| 转集合 | toList / toSet / toMap / toCollection | 收集为集合 |
| 拼接 | joining | 拼接为字符串 |
| 分组 | groupingBy | 按条件分组 |
| 分区 | partitioningBy | 按 true/false 分区 |
| 统计 | counting / summing / averaging / summarizing | 聚合统计 |
| 映射 | mapping | 收集时转换 |
二、转集合:toList / toSet / toMap
toList:收集为 List
List<String> titles = posts.stream()
.map(Post::getTitle)
.collect(Collectors.toList());
toSet:收集为 Set(自动去重)
Set<String> authors = posts.stream()
.map(Post::getAuthor)
.collect(Collectors.toSet());
toMap:收集为 Map
// Map<标题, 阅读量>
Map<String, Integer> titleViews = posts.stream()
.collect(Collectors.toMap(Post::getTitle, Post::getViews));
💡 注意:toMap 要求 key 唯一。如果有重复 key,需要传入第三个参数处理冲突:
Collectors.toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)
三、joining:字符串拼接
// 简单拼接
String all = posts.stream()
.map(Post::getTitle)
.collect(Collectors.joining(", "));
// "Lambda 入门, Stream 详解, Vue3 API, ..."
// 带前后缀
String tagged = posts.stream()
.map(Post::getTitle)
.collect(Collectors.joining(" | ", "【", "】"));
// "【Lambda 入门 | Stream 详解 | ...】"
三个参数分别是:分隔符、前缀、后缀。等价于 JS 的 arr.join(", ") 但更灵活。
四、groupingBy:分组
这是 Collectors 最强大的功能之一。
基础分组
// 按作者分组
Map<String, List<Post>> byAuthor = posts.stream()
.collect(Collectors.groupingBy(Post::getAuthor));
分组 + 计数
Map<String, Long> authorCount = posts.stream()
.collect(Collectors.groupingBy(Post::getAuthor, Collectors.counting()));
// {张三=3, 李四=2, 王五=2}
分组 + 平均值
Map<String, Double> authorAvgViews = posts.stream()
.collect(Collectors.groupingBy(Post::getAuthor,
Collectors.averagingInt(Post::getViews)));
// {张三=243.3, 李四=450.0, 王五=275.0}
多级分组
// 先按作者分,再按是否发布分
Map<String, Map<Boolean, Long>> result = posts.stream()
.collect(Collectors.groupingBy(
Post::getAuthor,
Collectors.groupingBy(Post::isPublished, Collectors.counting())
));
// 张三: {已发布=2, 未发布=1}
// 李四: {已发布=2}
五、partitioningBy:分区
分区是分组的一种特殊情况,按 true/false 分为两组。
Map<Boolean, List<Post>> partitioned = posts.stream()
.collect(Collectors.partitioningBy(Post::isPublished));
// partitioned.get(true) -> 已发布的列表
// partitioned.get(false) -> 未发布的列表
分区 + 统计:
Map<Boolean, Double> avgViews = posts.stream()
.collect(Collectors.partitioningBy(Post::isPublished,
Collectors.averagingInt(Post::getViews)));
// true -> 已发布平均阅读 350
// false -> 未发布平均阅读 215
六、summarizing:一键获取所有统计信息
IntSummaryStatistics stats = posts.stream()
.collect(Collectors.summarizingInt(Post::getViews));
stats.getCount(); // 7
stats.getSum(); // 2180
stats.getAverage(); // 311.4
stats.getMax(); // 500
stats.getMin(); // 180
一个方法同时获取计数、总和、平均值、最大值、最小值,比分别调用五次高效得多。
七、运行结果
=== toList / toSet / toMap ===
toList: [Lambda 入门, Stream 详解, Vue3 API, React Hooks, ...]
toSet (作者去重): [李四, 张三, 王五]
toMap (标题->阅读量): {Lambda 入门=200, Vue3 API=500, ...}
=== groupingBy:分组 ===
张三: 3 篇
李四: 2 篇
王五: 2 篇
按作者平均阅读: {李四=450.0, 张三=243.3, 王五=275.0}
=== partitioningBy:分区 ===
已发布: 5 篇
未发布: 2 篇
已发布平均阅读: 350
未发布平均阅读: 215
=== summarizingInt:汇总统计 ===
文章总数: 7
总阅读量: 2180
平均阅读: 311
最高阅读: 500
最低阅读: 180
=== 多级分组 ===
作者->发布状态->数量:
张三: 已发布=2, 未发布=1
李四: 已发布=2, 未发布=null
王五: 已发布=1, 未发布=1
八、Collectors 速查表
| 收集器 | 返回类型 | 用途 |
|---|---|---|
| toList | List | 收集为列表 |
| toSet | Set | 收集为集合(去重) |
| toMap | Map<K,V> | 收集为映射 |
| joining | String | 拼接为字符串 |
| groupingBy | Map<K,List> | 按条件分组 |
| partitioningBy | Map<Boolean,List> | 按真假分区 |
| counting | Long | 计数 |
| summingInt | Integer | 求和 |
| averagingInt | Double | 平均值 |
| summarizingInt | IntSummaryStatistics | 汇总统计 |
| mapping | 配合 groupingBy 使用 | 收集时转换 |
九、下篇预告
周四我们将深入 flatMap 的高级用法:一对多场景、标签提取、嵌套集合处理等实战技巧。
系列文章导航:
更多请关注我的微信公众号,感谢!