320. Java Stream API - 自定义 Collector

0 阅读2分钟

320. Java Stream API - 自定义 Collector(Creating Your Own Collector)

Java 提供了非常强大的 Collector API,可以用来灵活地处理和聚合流(Stream)中的数据。虽然我们通常使用 Collectors 工具类中的工厂方法(如 toList()groupingBy() 等),但理解并能够自己创建 Collector,将使你对 Stream 的工作机制有更深入的掌握,特别是在需要进行自定义收集逻辑时。


🧠 什么是 Collector?

一个 Collector 本质上由 四个组件 组成:

组件名类型用途说明
supplierSupplier<A>创建一个空的容器(比如 List、Set)
accumulatorBiConsumer<A, T>将流中的一个元素加入到容器中
combinerBinaryOperator<A>合并两个部分容器(用于并行流)
finisherFunction<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 合并所有结果。


🎓 小结

角色接口作用
SupplierSupplier<A>创建空的容器
AccumulatorBiConsumer<A, T>ObjIntConsumer<A>将元素加入容器
CombinerBinaryOperator<A>BiConsumer<A, A>合并两个容器
FinisherFunction<A, R>最终转换结果(如不可变化)

💡 进阶思考:第四个组件 finisher 会在一些复合收集器中用到,比如:

Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList);

它的作用是对最终容器做一层“后处理”。