324. Java Stream API - 实现 Collector 接口:自定义你的流式收集器
在 Java Stream API 中,Collector 是终端操作 collect() 背后的核心接口。如果你希望完全掌控数据收集的过程------比如构造特定的数据结构、在并行流中定制合并方式等------那就需要了解如何实现这个接口。
🔍 为什么要实现 Collector 接口?
虽然大多数时候我们可以使用 Java 提供的标准收集器(如 toList()、groupingBy()、joining()),但当你需要:
- 创建一个自定义结构(如:合并值到一个特殊格式的字符串、构造非标准 Map 等)
- 控制并行流合并逻辑(比如收集到线程安全容器)
- 优化性能(跳过不必要的转换)
那么实现 Collector 接口就是最灵活且强大的方式。
☕ 自定义 Collector 的三种方式
| 方法 | 描述 | 适用场景 |
|---|---|---|
✅ 使用 Collectors 工厂方法 |
组合已有收集器,或配合 collectingAndThen() 做轻度自定义 |
推荐优先使用 |
✅ 使用 Stream.collect(supplier, accumulator, combiner) |
自行传入构造、累加与合并逻辑 | 中等灵活性 |
✅❗ 实现 Collector<T, A, R> 接口 |
完全掌控所有阶段,适合高度定制 | 最灵活,但技术门槛高 |
📐 理解 Collector<T, A, R> 的三个类型参数
我们先看一下接口定义:
java
interface Collector<T, A, R> {
...
}
T:流中元素的类型A:中间可变容器类型(Accumulator 类型)R:最终返回结果类型(Result 类型)
🚗 举例说明
| 收集器 | T(流中元素) | A(中间容器) | R(最终结果) |
|---|---|---|---|
toList() |
String |
ArrayList<String> |
List<String> |
toSet() |
String |
HashSet<String> |
Set<String> |
groupingBy(String::length, counting()) |
String |
内部 Map + Counter | Map<Integer, Long> |
➡️ 注意:我们平时用 Collectors.toList() 等时,看不到 A 的具体类型,是因为返回类型中对它进行了隐藏(用 ? 代替了)。
📋 代码示例:查看 Collector 类型参数
java
Collection<String> strings = List.of(
"two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve"
);
// toList
Collector<String, ?, List<String>> listCollector = Collectors.toList();
List<String> list = strings.stream().collect(listCollector);
System.out.println("Size of list = " + list.size()); // 11
// toSet
Collector<String, ?, Set<String>> setCollector = Collectors.toSet();
Set<String> set = strings.stream().collect(setCollector);
System.out.println("Size of set = " + set.size()); // 11
// groupingBy + counting
Collector<String, ?, Map<Integer, Long>> groupingCollector =
Collectors.groupingBy(String::length, Collectors.counting());
Map<Integer, Long> map = strings.stream().collect(groupingCollector);
System.out.println("Size of map = " + map.size()); // 4
这些例子中,? 代表我们无需关心中间类型 A------由 API 实现者决定,我们只关心结果类型 R。
🧪 补充示例:自定义 Collector 实现(首字母统计)
我们实现一个 collector,用于按单词首字母统计出现次数:
java
class FirstLetterCountCollector implements Collector<String, Map<Character, Integer>, Map<Character, Integer>> {
@Override
public Supplier<Map<Character, Integer>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<Character, Integer>, String> accumulator() {
return (map, word) -> {
char first = word.charAt(0);
map.merge(first, 1, Integer::sum);
};
}
@Override
public BinaryOperator<Map<Character, Integer>> combiner() {
return (map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, Integer::sum));
return map1;
};
}
@Override
public Function<Map<Character, Integer>, Map<Character, Integer>> finisher() {
return Function.identity(); // 直接返回中间容器
}
@Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.IDENTITY_FINISH);
}
}
使用:
java
List<String> words = List.of("apple", "apricot", "banana", "blueberry", "cherry");
Map<Character, Integer> counts = words.stream()
.collect(new FirstLetterCountCollector());
System.out.println(counts); // {a=2, b=2, c=1}
🧠 总结:自定义 Collector 的 5 个核心方法
| 方法 | 作用 |
|---|---|
supplier() |
创建中间结果容器 |
accumulator() |
累加流中的每个元素 |
combiner() |
并行流中合并两个中间结果 |
finisher() |
将中间容器转换为最终结果 |
characteristics() |
提供 collector 的特性,如是否为并发、安全等 |
✅ Collector 特性(characteristics())常见值
| 特性 | 说明 |
|---|---|
CONCURRENT |
支持并发(accumulator 线程安全) |
UNORDERED |
不依赖顺序(如 Set 类型) |
IDENTITY_FINISH |
A 与 R 类型相同,finisher() 可直接返回 |