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()
相关推荐
灯澜忆梦14 小时前
GO_并发编程---定时器
开发语言·后端·golang
阳光是sunny14 小时前
从链到图:LangGraph 入门基础全解析
前端·人工智能·后端
皮皮林55115 小时前
开源实力派,给本地 AI 装上深度调研能力,一张 3090 跑到 95.7 分!
后端
小林ixn15 小时前
从 ??= 到 onKeyDown:一个 React 组件的“自我修养”
前端·javascript·react.js
努力的小雨15 小时前
把 Windows 桌面图标做成一个会自己运转的小世界
后端
REDcker15 小时前
显示分辨率标准对照详解
前端·网络·分辨率·显示·屏幕
এ慕ོ冬℘゜16 小时前
前端实战:jQuery 多条件联合搜索(标题模糊查询 + 日历时间段筛选)
前端·javascript·jquery
武子康16 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
腾渊信息科技公司16 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
一点一木16 小时前
从60首歌到1个网站:输入你的故事,还你一首歌
前端·github