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

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

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

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

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

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


✅ 引出 Finisher

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


🔧 什么是 Finisher?

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

例如:

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()
相关推荐
迷途小羔羊7 小时前
环境搭建
前端·javascript·后端
SimonKing7 小时前
DeepSeek 新模型免费白嫖,夯夯夯,赶紧用起来
java·后端·程序员
夕阳_醉了7 小时前
让 AI 回答“打字般”浮现:用 Vue 3 + DeepSeek 实现流式聊天
前端·vue.js·人工智能
打工仔折腾 AI7 小时前
介入式脑机接口的跨尺度解码与闭环调控:从算法落地到后端工程实践
人工智能·后端·python·深度学习·算法·ai agent 实战
SabrinaW7117 小时前
基于 C++17 与 RTTR 的注册式对象工厂:配置驱动的动态对象创建
java·前端·数据库
喵个咪7 小时前
GoWind Ops 深度拆解:纯出站 Agent + MQTT 信令,把「管机器、跑项目、发脚本、构建分发」做扎实
后端·go·自动化运维
Amos_Web8 小时前
Rspack 源码解析(二十九):CopyRspackPlugin 如何用 glob 把静态文件复制进产物
前端
鸠摩智首席音效师8 小时前
如何清理 ASP.NET 临时文件 ?
后端·asp.net
铁皮饭盒8 小时前
刷到 FDE 岗位那一刻,我还以为前端又活了
前端·后端