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()
相关推荐
deli00733 分钟前
32K 上下文塞满后,AI 最先忘掉什么?120 轮对话实测
前端
deli0072 小时前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng6 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
脚本自动化开发6 小时前
玩影之刃零,两张四千元级显卡怎么挑
后端
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈9 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技10 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户479492835691510 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
努力的小雨11 小时前
别再复制日志给AI了!我让Codex自己去观测云查报错
后端
风骏时光牛马12 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端