JDK12 Collectors.teeing 你真的需要了解一下

日拱一兵發表於2020-01-14

JDK12 Collectors.teeing 你真的需要了解一下

前言

在 Java 12 裡面有個非常好用但在官方 JEP 沒有公佈的功能,因為它只是 Collector 中的一個小改動,它的作用是 merge 兩個 collector 的結果,這句話顯得很抽象,老規矩,我們先來看個圖(這真是一個不和諧的圖????):JDK12 Collectors.teeing 你真的需要了解一下

管道改造經常會用這個小東西,通常我們叫它「三通」,它的主要作用就是將 downstream1 和 downstream2 的流入合併,然後從 merger 流出

有了這個形象的說明我們就進入正題吧「文中程式碼舉例比較多,更好的閱讀體驗點選文末——閱讀原文」

Collectors.teeing

上面提到的小功能就是 Collectors.teeing API, 先來看一下 JDK 關於該 API 的說明,看著覺得難受的直接忽略,繼續向下看例子就好了:

/** * 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,也就是說接收兩個引數,並輸出一個值,請看它的 apply 方法

@FunctionalInterfacepublic 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 說明,相信你會柳暗花明了

計數和累加

先來看一個經典的問題,給定的數字集合,需要對映整數流中的元素數量和它們的和

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 的靜態方法 counting 進行集合計數

  • downstream2 通過 Collectors 的靜態方法 summingInt 進行集合元素值的累加

  • merger 通過 CountSum 構造器收集結果

執行結果:

CountSum{count=7, sum=46}

我們通過 teeing 一次性得到我們想要的結果,繼續向下看其他例子:

最大值與最小值

通過給定的集合, 一次性計算出集合的最大值與最小值,同樣新建一個類 MinMax,並建立構造器用於 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 的靜態方法 minBy,通過 Comparator 比較器按照自然排序找到最小值

  • downstream2 通過 Collectors 的靜態方法 maxBy,通過 Comparator 比較器按照自然排序找到最大值

  • merger 通過 MinMax 構造器收集結果,只不過為了應對 NPE,將 BiFunction 的兩個入參經過 Optional 處理

執行結果:

MinMax{min=1, max=12}

為了驗證一下 Optional,我們將集合中新增一個 null 元素,並修改一下排序規則來看一下排序結果:

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 處理規則是將 null 放在排序的最前面

  • downstream2 處理規則是將 null 放在排序的最後面

  • merger 處理時,都會執行 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 的靜態方法 summingInt 做重量累加

  • downstream2 通過 Collectors 的靜態方法 mapping 提取出瓜的重量,並通過流的終結操作 toList() 獲取結果

  • merger 通過 WeightsAndTotal 構造器獲取結果

執行結果:

WeightsAndTotal{totalWeight=19500, weights=[1200, 3000, 2600, 1600, 1200, 2600, 1700, 3000, 2600]}

繼續一個更貼合實際的例子吧:

預約人員列表和預約人數

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 方法計數累加

  • merger 通過 EventParticipation 構造器收集結果

其中我們定義了 var result 來收集結果,並沒有指定型別,這個語法糖也加速了我們程式設計的效率

執行結果:

EventParticipation { guests = [Marco, Roger], total number of participants = 11 }

總結

其實 teeing API 就是靈活應用 Collectors 裡面定義的靜態方法,將集合元素通過 downstream1 和 downstream2 進行處理,最終通過 merger 收集起來,當專案中有同時獲取兩個收集結果時,是時候應用我們的 teeing API 了

全部程式碼獲取:公眾號回覆「demo」,開啟連結,訪問 jdk12-demo 資料夾即可
提前發現更多精彩,請訪問: https://dayarch.top

JDK12 Collectors.teeing 你真的需要了解一下

靈魂追問

  1. Collectors 裡面的靜態方法你應用的熟練嗎?

  2. 專案中你們在用 JDK 的版本是多少?

  3. Lambda 的使用熟練嗎?

  4. 你的燈還亮著嗎?


JDK12 Collectors.teeing 你真的需要了解一下


JDK12 Collectors.teeing 你真的需要了解一下

❤️「轉發」「在看」,是對我最大的支援❤️

JDK12 Collectors.teeing 你真的需要了解一下點選 "閱讀原文" 獲取更多資源!

相關文章