320. Java Stream API - 自定义 Collector(Creating Your Own Collector)
Java 提供了非常强大的 Collector API,可以用来灵活地处理和聚合流(Stream)中的数据。虽然我们通常使用 Collectors 工具类中的工厂方法(如 toList()、groupingBy() 等),但理解并能够自己创建 Collector,将使你对 Stream 的工作机制有更深入的掌握,特别是在需要进行自定义收集逻辑时。
🧠 什么是 Collector?
一个 Collector 本质上由 四个组件 组成:
| 组件名 | 类型 | 用途说明 |
|---|---|---|
supplier | Supplier<A> | 创建一个空的容器(比如 List、Set) |
accumulator | BiConsumer<A, T> | 将流中的一个元素加入到容器中 |
combiner | BinaryOperator<A> | 合并两个部分容器(用于并行流) |
finisher | Function<A, R> | 对最终容器做后处理(可选) |
💡 注意:对于大多数收集操作,前三个组件就足够了;第四个
finisher只在需要转换结果类型时才使用(比如collectingAndThen())。
🧩 理解各个组件
1️⃣ Supplier:创建容器
Supplier<List<Integer>> supplier = ArrayList::new;
这个 Supplier 会在开始时提供一个空容器(比如 new ArrayList<>()),用于存放收集结果。
2️⃣ Accumulator:累加元素
ObjIntConsumer<List<Integer>> accumulator = List::add;
这个累加器的作用是将流中的每个元素添加到容器中。
在 IntStream.collect() 中,它是 ObjIntConsumer<A> 类型,因为原始类型 int 被直接传入,而不是自动装箱。
3️⃣ Combiner:合并子结果(用于并行流)
BiConsumer<List<Integer>, List<Integer>> combiner = List::addAll;
并行流会将数据划分为多个子流并发处理,因此会产生多个部分结果容器。combiner 的作用就是把这些容器合并。
🛠 示例:将 IntStream 收集为 List<Integer>
以下代码展示了如何用上述三个组件手动构造 collect() 操作:
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class CustomCollectorDemo {
public static void main(String[] args) {
// 第一步:定义组件
Supplier<List<Integer>> supplier = ArrayList::new;
ObjIntConsumer<List<Integer>> accumulator = List::add;
BiConsumer<List<Integer>, List<Integer>> combiner = List::addAll;
// 第二步:执行收集
List<Integer> collect = IntStream.range(0, 10)
.collect(supplier, accumulator, combiner);
System.out.println("collect = " + collect);
}
}
✅ 输出:
collect = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
🔁 更改容器类型 —— 收集为 Set
如果我们想把这些元素收集到一个 Set 中,只需要修改 supplier 和类型声明即可:
Supplier<Set<Integer>> supplier = HashSet::new;
ObjIntConsumer<Set<Integer>> accumulator = Set::add;
BiConsumer<Set<Integer>, Set<Integer>> combiner = Set::addAll;
Set<Integer> collect = IntStream.range(0, 10)
.collect(supplier, accumulator, combiner);
System.out.println("Set = " + collect);
🌐 并行流说明(Parallel Stream)
并行流的处理会将数据划分为子流,每个子流使用自己的容器进行收集。比如:
IntStream.range(0, 10)
.parallel()
.collect(supplier, accumulator, combiner);
在并行收集时,每个线程维护自己的容器,最后通过 combiner 合并所有结果。
🎓 小结
| 角色 | 接口 | 作用 |
|---|---|---|
| Supplier | Supplier<A> | 创建空的容器 |
| Accumulator | BiConsumer<A, T> 或 ObjIntConsumer<A> | 将元素加入容器 |
| Combiner | BinaryOperator<A> 或 BiConsumer<A, A> | 合并两个容器 |
| Finisher | Function<A, R> | 最终转换结果(如不可变化) |
💡 进阶思考:第四个组件 finisher 会在一些复合收集器中用到,比如:
Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList);
它的作用是对最终容器做一层“后处理”。