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)
📌 简单来说,它做了三件事:
- 用第一个 collector 收集数据
- 同时用第二个 collector 收集相同数据
- 用合并函数将两个结果整合为一个结果返回
🚗🚚 实战:筛选电动车辆(Electric Vehicles)
我们有一个 Vehicle 接口,分别由 Car 和 Truck 两个类实现。
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)
- 包括
Car和Truck
- 包括
- 并将它们合并成一个集合
传统方式要写两次 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()
这样 cars 和 trucks 都不被修改,返回的是新合并的结果。
🧰 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);