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 函数)
它会:
- 先执行基础的 collector 操作
- 然后将结果传给
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() |