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()
相关推荐
何中应1 小时前
记录一次pom.xml依赖顺序产生的错误
后端·maven
dfyx9991 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
3GPP仿真实验室2 小时前
6G 物理层变天AFDM:与其在 OFDM 的死胡同里撞墙,不如换个坐标系“折叠”世界
前端
Jing_Rainbow2 小时前
【React-9/Lesson93(2025-12-30)】React Hooks 深度解析:从基础到实战🎯
前端·javascript·react.js
We་ct2 小时前
LeetCode 2. 两数相加:链表经典应用题详解
前端·算法·leetcode·链表·typescript
风的归宿552 小时前
一次openresty的网关性能优化之旅
后端
芝加哥兔兔养殖场2 小时前
前端/iOS开发者必备工具软件合集
前端·ios
web打印社区2 小时前
web-print-pdf:专为Web打印而生的专业解决方案
前端·javascript·vue.js·electron·html
糖糖TANG2 小时前
学成在线 案例练习
前端·css