Java12 Collectors.teeing你真的需要了解一下
前言
在 Java 12 里面有個非常好用但在官方 JEP 沒有公布的功能,因為它只是 Collector 中的一個小改動,它的作用是 merge 兩個 collector 的結果,這句話顯得很抽象,老規(guī)矩,我們先來看個圖:
管道改造經常會用這個小東西,通常我們叫它「三通」,它的主要作用就是將 downstream1 和 downstream2 的流入合并,然后從 merger 流出
有了這個形象的說明我們就進入正題吧
Collectors.teeing
上面提到的小功能就是 Collectors.teeing API, 先來看一下 JDK 關于該 API 的說明,看著覺得難受的直接忽略,繼續(xù)向下看例子就好了:
- /**
- * Returns a {@code Collector} that is a composite of two downstream collectors.
- * Every element passed to the resulting collector is processed by both downstream
- * collectors, then their results are merged using the specified merge function
- * into the final result.
- *
- * <p>The resulting collector functions do the following:
- *
- * <ul>
- * <li>supplier: creates a result container that contains result containers
- * obtained by calling each collector's supplier
- * <li>accumulator: calls each collector's accumulator with its result container
- * and the input element
- * <li>combiner: calls each collector's combiner with two result containers
- * <li>finisher: calls each collector's finisher with its result container,
- * then calls the supplied merger and returns its result.
- * </ul>
- *
- * <p>The resulting collector is {@link Collector.Characteristics#UNORDERED} if both downstream
- * collectors are unordered and {@link Collector.Characteristics#CONCURRENT} if both downstream
- * collectors are concurrent.
- *
- * @param <T> the type of the input elements
- * @param <R1> the result type of the first collector
- * @param <R2> the result type of the second collector
- * @param <R> the final result type
- * @param downstream1 the first downstream collector
- * @param downstream2 the second downstream collector
- * @param merger the function which merges two results into the single one
- * @return a {@code Collector} which aggregates the results of two supplied collectors.
- * @since 12
- */
- 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) {
- return teeing0(downstream1, downstream2, merger);
- }
API 描述重的一句話非常關鍵:
Every element passed to the resulting collector is processed by both downstream collectors
結合「三通圖」來說明就是,集合中每一個要被傳入 merger 的元素都會經過 downstream1 和 downstream2 的加工處理
其中 merger 類型是 BiFunction,也就是說接收兩個參數(shù),并輸出一個值,請看它的 apply 方法
- @FunctionalInterface
- public interface BiFunction<T, U, R> {
- /**
- * Applies this function to the given arguments.
- *
- * @param t the first function argument
- * @param u the second function argument
- * @return the function result
- */
- R apply(T t, U u);
- }
至于可以如何處理,我們來看一些例子吧
例子
為了更好的說明 teeing 的使用,列舉了四個例子,看過這四個例子再回看上面的 API 說明,相信你會柳暗花明了
計數(shù)和累加
先來看一個經典的問題,給定的數(shù)字集合,需要映射整數(shù)流中的元素數(shù)量和它們的和
- class CountSum {
- private final Long count;
- private final Integer sum;
- public CountSum(Long count, Integer sum) {
- this.count = count;
- this.sum = sum;
- }
- @Override
- public String toString() {
- return "CountSum{" +
- "count=" + count +
- ", sum=" + sum +
- '}';
- }
- }
通過 Collectors.teeing 處理
- CountSum countsum = Stream.of(2, 11, 1, 5, 7, 8, 12)
- .collect(Collectors.teeing(
- counting(),
- summingInt(e -> e),
- CountSum::new));
- System.out.println(countsum.toString());
- downstream1 通過 Collectors 的靜態(tài)方法 counting 進行集合計數(shù)
- downstream2 通過 Collectors 的靜態(tài)方法 summingInt 進行集合元素值的累加
- merger 通過 CountSum 構造器收集結果
運行結果:
- CountSum{count=7, sum=46}
我們通過 teeing 一次性得到我們想要的結果,繼續(xù)向下看其他例子:
最大值與最小值
通過給定的集合, 一次性計算出集合的最大值與最小值,同樣新建一個類 MinMax,并創(chuàng)建構造器用于 merger 收集結果
- class MinMax {
- private final Integer min;
- private final Integer max;
- public MinMax(Integer min, Integer max) {
- this.min = min;
- this.max = max;
- }
- @Override
- public String toString() {
- return "MinMax{" +
- "min=" + min +
- ", max=" + max +
- '}';
- }
- }
通過 teeing API 計算結果:
- MinMax minmax = Stream.of(2, 11, 1, 5, 7, 8, 12)
- .collect(Collectors.teeing(
- minBy(Comparator.naturalOrder()),
- maxBy(Comparator.naturalOrder()),
- (Optional<Integer> a, Optional<Integer> b) -> new MinMax(a.orElse(Integer.MIN_VALUE), b.orElse(Integer.MAX_VALUE))));
- System.out.println(minmax.toString());
- downstream1 通過 Collectors 的靜態(tài)方法 minBy,通過 Comparator 比較器按照自然排序找到最小值
- downstream2 通過 Collectors 的靜態(tài)方法 maxBy,通過 Comparator 比較器按照自然排序找到最大值
- merger 通過 MinMax 構造器收集結果,只不過為了應對 NPE,將 BiFunction 的兩個入?yún)⒔涍^ Optional 處理
運行結果:
- MinMax{min=1, max=12}
為了驗證一下 Optional,我們將集合中添加一個 null 元素,并修改一下排序規(guī)則來看一下排序結果:
- MinMax minmax = Stream.of(null, 2, 11, 1, 5, 7, 8, 12)
- .collect(Collectors.teeing(
- minBy(Comparator.nullsFirst(Comparator.naturalOrder())),
- maxBy(Comparator.nullsLast(Comparator.naturalOrder())),
- (Optional<Integer> a, Optional<Integer> b) -> new MinMax(a.orElse(Integer.MIN_VALUE), b.orElse(Integer.MAX_VALUE))));
- downstream1 處理規(guī)則是將 null 放在排序的最前面
- downstream2 處理規(guī)則是將 null 放在排序的最后面
- merger 處理時,都會執(zhí)行 optional.orElse 方法,分別輸出最小值與最大值
運行結果:
- MinMax{min=-2147483648, max=2147483647}
瓜的總重和單個重量
接下來舉一個更貼合實際的操作對象的例子
- // 定義瓜的類型和重量
- class Melon {
- private final String type;
- private final int weight;
- public Melon(String type, int weight) {
- this.type = type;
- this.weight = weight;
- }
- public String getType() {
- return type;
- }
- public int getWeight() {
- return weight;
- }
- }
- // 總重和單個重量列表
- class WeightsAndTotal {
- private final int totalWeight;
- private final List<Integer> weights;
- public WeightsAndTotal(int totalWeight, List<Integer> weights) {
- this.totalWeight = totalWeight;
- this.weights = weights;
- }
- @Override
- public String toString() {
- return "WeightsAndTotal{" +
- "totalWeight=" + totalWeight +
- ", weights=" + weights +
- '}';
- }
- }
通過 teeing API 計算總重量和單個列表重量
- List<Melon> melons = Arrays.asList(new Melon("Crenshaw", 1200),
- new Melon("Gac", 3000), new Melon("Hemi", 2600),
- new Melon("Hemi", 1600), new Melon("Gac", 1200),
- new Melon("Apollo", 2600), new Melon("Horned", 1700),
- new Melon("Gac", 3000), new Melon("Hemi", 2600)
- );
- WeightsAndTotal weightsAndTotal = melons.stream()
- .collect(Collectors.teeing(
- summingInt(Melon::getWeight),
- mapping(m -> m.getWeight(), toList()),
- WeightsAndTotal::new));
- System.out.println(weightsAndTotal.toString());
- downstream1 通過 Collectors 的靜態(tài)方法 summingInt 做重量累加
- downstream2 通過 Collectors 的靜態(tài)方法 mapping 提取出瓜的重量,并通過流的終結操作 toList() 獲取結果
- merger 通過 WeightsAndTotal 構造器獲取結果
運行結果:
- WeightsAndTotal{totalWeight=19500, weights=[1200, 3000, 2600, 1600, 1200, 2600, 1700, 3000, 2600]}
繼續(xù)一個更貼合實際的例子吧:
預約人員列表和預約人數(shù)
- class Guest {
- private String name;
- private boolean participating;
- private Integer participantsNumber;
- public Guest(String name, boolean participating, Integer participantsNumber) {
- this.name = name;
- this.participating = participating;
- this.participantsNumber = participantsNumber;
- }
- public boolean isParticipating() {
- return participating;
- }
- public Integer getParticipantsNumber() {
- return participantsNumber;
- }
- public String getName() {
- return name;
- }
- }
- class EventParticipation {
- private List<String> guestNameList;
- private Integer totalNumberOfParticipants;
- public EventParticipation(List<String> guestNameList, Integer totalNumberOfParticipants) {
- this.guestNameList = guestNameList;
- this.totalNumberOfParticipants = totalNumberOfParticipants;
- }
- @Override
- public String toString() {
- return "EventParticipation { " +
- "guests = " + guestNameList +
- ", total number of participants = " + totalNumberOfParticipants +
- " }";
- }
- }
通過 teeing API 處理
- var result = Stream.of(
- new Guest("Marco", true, 3),
- new Guest("David", false, 2),
- new Guest("Roger",true, 6))
- .collect(Collectors.teeing(
- Collectors.filtering(Guest::isParticipating, Collectors.mapping(Guest::getName, Collectors.toList())),
- Collectors.summingInt(Guest::getParticipantsNumber),
- EventParticipation::new
- ));
- System.out.println(result);
- downstream1 通過 filtering 方法過濾出確定參加的人,并 mapping 出他們的姓名,最終放到 toList 集合中
- downstream2 通過 summingInt 方法計數(shù)累加
- merger 通過 EventParticipation 構造器收集結果
其中我們定義了 var result 來收集結果,并沒有指定類型,這個語法糖也加速了我們編程的效率
運行結果:
- EventParticipation { guests = [Marco, Roger], total number of participants = 11 }
總結
其實 teeing API 就是靈活應用 Collectors 里面定義的靜態(tài)方法,將集合元素通過 downstream1 和 downstream2 進行處理,最終通過 merger 收集起來,當項目中有同時獲取兩個收集結果時,是時候應用我們的 teeing API 了。