322. Java Stream API - 使用 Finisher 对 Collector 结果进行后处理

322. Java Stream API - 使用 Finisher 对 Collector 结果进行后处理

在前面小节中,我们已经学习了如何用 StringBufferIntStream 的元素拼接成字符串。但你可能注意到,我们需要手动调用 .toString()StringBuffer 转换为最终结果 String

而使用 Collectors.joining() 时,这个转换是自动完成的:

java 复制代码
Stream.of("a", "b", "c").collect(Collectors.joining()); // "abc"

那么,Collectors.joining() 是怎么做到的呢?


✅ 引出 Finisher

答案就是:使用了 Collector API 中的第四个组件------finisher


🔧 什么是 Finisher?

finisher 是一个函数,它的职责是将内部可变容器(如 StringBufferListMap 等)转换为你最终想要的返回结果。

例如:

java 复制代码
Function<StringBuffer, String> finisher = StringBuffer::toString;

📌 Collectors.joining() 就是在内部用这个 finisher 将 StringBuffer 转为 String 的!


🤔 所有 Collector 都需要 finisher 吗?

不是的!

有些 collector 使用的是 identity finisher,也就是直接返回内部容器本身,无需转换:

java 复制代码
Collectors.toList()
Collectors.toSet()
Collectors.groupingBy()
Collectors.toMap()

但当你希望:

  • 返回 不可变集合
  • 提取某个值(如最大值、最小值)
  • 或进行 后置转换(post-processing)

👉 就必须使用 finisher 来完成最后一步。


🚀 使用 Collectors.collectingAndThen() 来封装 finisher

Java 提供了一个便捷方法 Collectors.collectingAndThen(),它接受两个参数:

java 复制代码
Collectors.collectingAndThen(基础 collector, finisher 函数)

它会:

  1. 先执行基础的 collector 操作
  2. 然后将结果传给 finisher 函数进行后处理

📊 示例:查找字符串长度出现次数最多的长度(Histogram Max)

我们有一组字符串,想统计每个字符串长度出现的次数,然后找出出现次数最多的长度:

原始做法(两步):

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

// 第一步:构造 histogram(Map<Integer, Long>)
Map<Integer, Long> histogram = strings.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.counting()));

// 第二步:从 histogram 中找出最大值
Map.Entry<Integer, Long> maxValue = histogram.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .orElseThrow();

System.out.println("maxValue = " + maxValue);

🔁 改造:用 collectingAndThen 一步完成

java 复制代码
Function<Map<Integer, Long>, Map.Entry<Integer, Long>> finisher =
    map -> map.entrySet().stream()
              .max(Map.Entry.comparingByValue())
              .orElseThrow();

Map.Entry<Integer, Long> maxValue = strings.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(String::length, Collectors.counting()),
        finisher
    ));

System.out.println("maxValue = " + maxValue);

💡 输出:

java 复制代码
maxValue = 3=3

表示长度为 3 的字符串一共有 3 个,是出现最多的。


📦 为什么要这么写?看似更复杂?

虽然初看起来更复杂,但将"找最大值"作为一个 collector 抽象出来,意味着你可以:

  • ✅ 把它作为一个可复用组件使用
  • ✅ 作为 downstream collector 传递给 groupingBy()mapping() 等组合操作

这就为数据处理提供了极大的灵活性和组合能力。


🔚 小结:Finisher 的应用场景

使用场景 示例
从可变容器转为结果对象 StringBuffer -> String
生成不可变集合 List -> Collections.unmodifiableList(list)
提取统计信息(如最大值) Map -> max entry
二次转换收集结果 使用 collectingAndThen()
相关推荐
Tony Bai1 小时前
Rust 看了流泪,AI 看了沉默:扒开 Go 泛型最让你抓狂的“残疾”类型推断
开发语言·人工智能·后端·golang·rust
用户3167361303421 小时前
javaLangchain4j从官方文档入手,看他做了什么——具体使用(二)
后端
無名路人1 小时前
Zsh 脚本 + VS Code 任务:NestJS + Vue3 一键部署到 1Panel
运维·后端·自动化运维
猩猩程序员1 小时前
Pretext:一个绕过 DOM 的纯 JavaScript 排版引擎
前端
竹林8181 小时前
从“连接失败”到丝滑登录:我用 ethers.js 连接 MetaMask 的完整踩坑实录
前端·javascript
神舟之光1 小时前
jwt权限控制简单总结(乡村意见簿-vue-express-mongdb)
前端·vue.js·express
铭毅天下2 小时前
EasySearch Rules 规则语法速查手册
开发语言·前端·javascript·ecmascript
GISer_Jing2 小时前
AI Agent操作系统架构师:Harness Engineer解析
前端·人工智能·ai·aigc
英俊潇洒美少年2 小时前
css中专门用来提升渲染性能、减少重排重绘的属性
前端·css
ybwycx2 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch