JDK12 Collectors.teeing 你真的需要了解一下
前言
在 Java 12 裡面有個非常好用但在官方 JEP 沒有公佈的功能,因為它只是 Collector 中的一個小改動,它的作用是 merge 兩個 collector 的結果,這句話顯得很抽象,老規矩,我們先來看個圖(這真是一個不和諧的圖????):
管道改造經常會用這個小東西,通常我們叫它「三通」,它的主要作用就是將 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 方法
@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 說明,相信你會柳暗花明了
計數和累加
先來看一個經典的問題,給定的數字集合,需要對映整數流中的元素數量和它們的和
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
靈魂追問
Collectors 裡面的靜態方法你應用的熟練嗎?
專案中你們在用 JDK 的版本是多少?
Lambda 的使用熟練嗎?
你的燈還亮著嗎?
❤️「轉發」和「在看」,是對我最大的支援❤️
點選 "閱讀原文" 獲取更多資源!
相關文章
- Java12 Collectors.teeing 你真的需要了解一下Java
- 你需要了解API介面API
- 關於深度學習,這些知識點你需要了解一下深度學習
- 保衛 Java 應用程式的安全沙箱機制你需要了解一下Java
- 你對CSS權重真的足夠了解嗎?CSS
- 你需要了解的 HTTP Status CodeHTTP
- 你需要了解的HTTP協議HTTP協議
- 當裸辭遇到面試難,這些面試題你需要了解一下面試題
- 關於深度學習編譯器,這些知識你需要了解一下深度學習編譯
- 你真的需要新款MacBook Pro嗎?Mac
- 特斯拉釋出Robotaxi,支撐其自動駕駛的FSD你需要了解一下自動駕駛
- 當裸辭遇到了面試難,你需要了解一下這些面試題面試題
- 精讀《Deno 1.0 你需要了解的》
- 執行緒池你真不來了解一下嗎?執行緒
- 關於等保2.0,你需要了解的
- 關於redis,你需要了解的幾點!Redis
- 在日本考慮投放電視廣告之前,你需要了解一下當地的情況
- 學習 webpack 前,你需要了解的那些概念Web
- 六款你需要了解的實用軟體
- 需要介面管理的你瞭解一下?
- 年底覆盤遊戲留存表現前,這 6 大使用者屬性你需要了解一下遊戲
- 資料安全認證你有幾個?來了解一下
- 面試 Netflix 前,你至少需要了解以下內容面試
- 接觸Mac初期,你需要了解的那點事Mac
- 20 道 Spring Boot 面試題你需要了解下Spring Boot面試題
- 更全面的記錄缺陷,你需要了解這些
- 你的資料庫真的需要遷移到雲嗎?資料庫
- 關於 React19,你需要了解的前因後果React
- 學習Flutter,你需要了解的Dart 編碼規範FlutterDart
- 小白必看!入門嵌入式你需要了解這些!
- 關於GDPR,你需要了解的的5件事
- [譯] 為什麼你需要關注一下 FlutterFlutter
- HTTP/3 來啦,你還在等什麼?趕緊了解一下HTTP
- SpringBoot2.1.0已釋出,7個重大更新你需要了解Spring Boot
- 你需要了解下Android View的更新requestLayout與重繪invalidateAndroidView
- PHP 7.4 新特性 - 預載入 (Preloading),你需要了解這些!PHP
- 從 Python 2 切換到 Python 3 你所需要了解的Python
- 到底什麼是分散式系統?你需要了解這些分散式