323. Java Stream API - 组合两个收集器的结果:Java 12 中的 Collectors.teeing()

3 阅读2分钟

323. Java Stream API - 组合两个收集器的结果:Java 12 中的 Collectors.teeing()

在 Java 12 中,Collectors 工具类新增了一个强大方法:teeing()

这个方法能让我们:

同时应用两个独立的收集器(Collectors)到同一个流上,并在末尾合并它们的结果。

非常适用于并行筛选和聚合场景


📦 teeing() 方法的签名

public static <T, R1, R2, R> Collector<T, ?, R> teeing(
    Collector<? super T, ?, R1> downstream1,
    Collector<? super T, ?, R2> downstream2,
    BiFunction<? super R1, ? super R2, R> merger)

📌 简单来说,它做了三件事:

  1. 用第一个 collector 收集数据
  2. 同时用第二个 collector 收集相同数据
  3. 用合并函数将两个结果整合为一个结果返回

🚗🚚 实战:筛选电动车辆(Electric Vehicles)

我们有一个 Vehicle 接口,分别由 CarTruck 两个类实现。

enum Color { RED, BLUE, WHITE, YELLOW }
enum Engine { ELECTRIC, HYBRID, GAS }
enum Drive { WD2, WD4 }

interface Vehicle {}

record Car(Color color, Engine engine, Drive drive, int passengers) implements Vehicle {}
record Truck(Engine engine, Drive drive, int weight) implements Vehicle {}

然后,我们有如下车辆列表:

List<Vehicle> vehicles = List.of(
    new Car(Color.BLUE, Engine.ELECTRIC, Drive.WD2, 4),
    new Car(Color.WHITE, Engine.HYBRID, Drive.WD4, 5),
    new Truck(Engine.GAS, Drive.WD4, 12_000),
    new Truck(Engine.GAS, Drive.WD2, 8_000)
);

🎯 目标:

  • 筛选出所有电动车(Electric Engine)
    • 包括 CarTruck
  • 并将它们合并成一个集合

传统方式要写两次 filter(),再手动合并;使用 teeing() 可以 一气呵成,一次遍历搞定!


✅ teeing() 示例代码

List<Vehicle> electricVehicles = vehicles.stream()
    .collect(Collectors.teeing(
        // 第一个 collector:筛选电动汽车
        Collectors.filtering(
            v -> v instanceof Car car && car.engine() == Engine.ELECTRIC,
            Collectors.toList()
        ),
        // 第二个 collector:筛选电动卡车
        Collectors.filtering(
            v -> v instanceof Truck truck && truck.engine() == Engine.ELECTRIC,
            Collectors.toList()
        ),
        // 合并两个结果
        (cars, trucks) -> {
            cars.addAll(trucks);
            return cars;
        }
    ));

System.out.println("Electric vehicles = " + electricVehicles);

💡 控制台输出:

Electric vehicles = [Car[color=BLUE, engine=ELECTRIC, drive=WD2, passengers=4]]

说明当前只有一辆电动汽车,结果正确✅。


📌 分析各个部分

位置作用
Collectors.filtering(...)对流元素进行筛选并继续收集
Collectors.toList()把筛选结果变成列表
(cars, trucks) -> { ... }用于合并两个中间结果的 BiFunction(双参数函数)

🧠 小技巧:合并函数的替代方式

如果你不想修改原始 cars 列表(即不可变合并),可以这样写:

(cars, trucks) -> Stream.concat(cars.stream(), trucks.stream()).toList()

这样 carstrucks 都不被修改,返回的是新合并的结果。


🧰 teeing() 的使用场景总结

场景示例
并行筛选不同子集electric cars vs electric trucks
计算两个指标平均值 & 总和,最大值 & 最小值
多路径聚合同时 groupBy 和 count,再合并

📚 补充示例:求最大值与最小值

record Person(String name, int age) {}

List<Person> people = List.of(
    new Person("Alice", 30),
    new Person("Bob", 24),
    new Person("Carol", 35)
);

Map.Entry<Person, Person> minMax = people.stream()
    .collect(Collectors.teeing(
        Collectors.minBy(Comparator.comparingInt(Person::age)),
        Collectors.maxBy(Comparator.comparingInt(Person::age)),
        (minOpt, maxOpt) -> Map.entry(minOpt.orElseThrow(), maxOpt.orElseThrow())
    ));

System.out.println("Youngest and Oldest: " + minMax);