Java 8 - 自定义Collector
文章目錄
- Pre
- Collector接口聲明的方法
- 理解 Collector接口中聲明的方法
- 1.建立新的結(jié)果容器: supplier 方法
- 2.將元素添加到結(jié)果容器: accumulator 方法
- 3.對(duì)結(jié)果容器應(yīng)用最終轉(zhuǎn)換: finisher 方法
- 4.合并兩個(gè)結(jié)果容器: combiner 方法
- 5. characteristics 方法
- 自定義Collector Demo
Pre
Collector 接口包含了一系列方法,為實(shí)現(xiàn)具體的歸約操作(即收集器)提供了范本。
我們已經(jīng)看過了 Collector 接口中實(shí)現(xiàn)的許多收集器,例如 toList 或 groupingBy 。這也意味著可以為 Collector 接口提供自己的實(shí)現(xiàn),從而自由地創(chuàng)建自定義歸約操作。
要開始使用 Collector 接口,我們先看看toList 工廠方法,它會(huì)把流中的所有元素收集成一個(gè) List 。我們當(dāng)時(shí)說在日常工作中經(jīng)常會(huì)用到這個(gè)收集器,而且它也是寫起來比較直觀的一個(gè),至少理論上如此。通過仔細(xì)研究這個(gè)收集器是怎么實(shí)現(xiàn)的。
我們可以很好地了解 Collector 接口是怎么定義的,以及它的方法所返回的函數(shù)在內(nèi)部是如何為collect 方法所用的。
Collector接口聲明的方法
首先讓我們?cè)谙旅娴牧斜碇锌纯?Collector 接口的定義,它列出了接口的簽名以及聲明的五個(gè)方法。
- T 是流中要收集的項(xiàng)目的泛型。
- A 是累加器的類型,累加器是在收集過程中用于累積部分結(jié)果的對(duì)象。
- R 是收集操作得到的對(duì)象(通常但并不一定是集合)的類型。
例如,你可以實(shí)現(xiàn)一個(gè) ToListCollector<T> 類,將 Stream<T> 中的所有元素收集到一個(gè)List<T> 里,它的簽名如下
public class ToListCollector<T> implements Collector<T, List<T>, List<T>>待會(huì)揭秘 這里用于累積的對(duì)象也將是收集過程的最終結(jié)果
理解 Collector接口中聲明的方法
現(xiàn)在我們可以一個(gè)個(gè)來分析Collector 接口聲明的五個(gè)方法了。通過分析,你會(huì)注意到,前四個(gè)方法都會(huì)返回一個(gè)會(huì)被 collect 方法調(diào)用的函數(shù),而第五個(gè)方法 characteristics 則提供了一系列特征,也就是一個(gè)提示列表,告訴 collect 方法在執(zhí)行歸約操作的時(shí)候可以應(yīng)用哪些優(yōu)化(比如并行化)。
1.建立新的結(jié)果容器: supplier 方法
supplier 方法必須返回一個(gè)結(jié)果為空的 Supplier ,也就是一個(gè)無參數(shù)函數(shù),在調(diào)用時(shí)它會(huì)創(chuàng)建一個(gè)空的累加器實(shí)例,供數(shù)據(jù)收集過程使用。
很明顯,對(duì)于將累加器本身作為結(jié)果返回的收集器,比如我們的 ToListCollector ,在對(duì)空流執(zhí)行操作的時(shí)候,這個(gè)空的累加器也代表了收集過程的結(jié)果。
在我們的 ToListCollector 中, supplier 返回一個(gè)空的 List ,如下所示:
public Supplier<List<T>> supplier() {return () -> new ArrayList<T>(); }請(qǐng)注意你也可以只傳遞一個(gè)構(gòu)造函數(shù)引用:
public Supplier<List<T>> supplier() { return ArrayList::new; }2.將元素添加到結(jié)果容器: accumulator 方法
accumulator 方法會(huì)返回執(zhí)行歸約操作的函數(shù)。當(dāng)遍歷到流中第n個(gè)元素時(shí),這個(gè)函數(shù)執(zhí)行時(shí)會(huì)有兩個(gè)參數(shù):保存歸約結(jié)果的累加器(已收集了流中的前 n-1 個(gè)項(xiàng)目),還有第n個(gè)元素本身。
該函數(shù)將返回 void ,因?yàn)槔奂悠魇窃桓?#xff0c;即函數(shù)的執(zhí)行改變了它的內(nèi)部狀態(tài)以體現(xiàn)遍歷的元素的效果。
對(duì)于 ToListCollector ,這個(gè)函數(shù)僅僅會(huì)把當(dāng)前項(xiàng)目添加至已經(jīng)遍歷過的項(xiàng)目的列表:
public BiConsumer<List<T>, T> accumulator() {return (list, item) -> list.add(item); }你也可以使用方法引用,這會(huì)更為簡(jiǎn)潔:
public BiConsumer<List<T>, T> accumulator() {return List::add; }3.對(duì)結(jié)果容器應(yīng)用最終轉(zhuǎn)換: finisher 方法
在遍歷完流后, finisher 方法必須返回在累積過程的最后要調(diào)用的一個(gè)函數(shù),以便將累加器對(duì)象轉(zhuǎn)換為整個(gè)集合操作的最終結(jié)果。
通常,就像 ToListCollector 的情況一樣,累加器對(duì)象恰好符合預(yù)期的最終結(jié)果,因此無需進(jìn)行轉(zhuǎn)換。所以 finisher 方法只需返回 identity 函數(shù):
public Function<List<T>, List<T>> finisher() {return Function.identity(); }這三個(gè)方法已經(jīng)足以對(duì)流進(jìn)行順序歸約,至少從邏輯上看可以按下圖進(jìn)行。
實(shí)踐中的實(shí)現(xiàn)細(xì)節(jié)可能還要復(fù)雜一點(diǎn),一方面是因?yàn)榱鞯难舆t性質(zhì),可能在 collect 操作之前還需要完成其他中間操作的流水線,另一方面則是理論上可能要進(jìn)行并行歸約。
4.合并兩個(gè)結(jié)果容器: combiner 方法
四個(gè)方法中的最后一個(gè)—— combiner 方法會(huì)返回一個(gè)供歸約操作使用的函數(shù),它定義了對(duì)
流的各個(gè)子部分進(jìn)行并行處理時(shí),各個(gè)子部分歸約所得的累加器要如何合并。
對(duì)于 toList 而言,這個(gè)方法的實(shí)現(xiàn)非常簡(jiǎn)單,只要把從流的第二個(gè)部分收集到的項(xiàng)目列表加到遍歷第一部分時(shí)得到的列表后面就行了:
public BinaryOperator<List<T>> combiner() {return (list1, list2) -> {list1.addAll(list2);return list1; } }有了這第四個(gè)方法,就可以對(duì)流進(jìn)行并行歸約了。它會(huì)用到Java 7中引入的分支/合并框架和Spliterator 抽象, 如下圖所示
5. characteristics 方法
最后一個(gè)方法—— characteristics 會(huì)返回一個(gè)不可變的 Characteristics 集合,它定義了收集器的行為——尤其是關(guān)于流是否可以并行歸約,以及可以使用哪些優(yōu)化的提示。
Characteristics 是一個(gè)包含三個(gè)項(xiàng)目的枚舉。
- UNORDERED ——?dú)w約結(jié)果不受流中項(xiàng)目的遍歷和累積順序的影響
- CONCURRENT —— accumulator 函數(shù)可以從多個(gè)線程同時(shí)調(diào)用,且該收集器可以并行歸約流。如果收集器沒有標(biāo)為 UNORDERED ,那它僅在用于無序數(shù)據(jù)源時(shí)才可以并行歸約。
- IDENTITY_FINISH ——這表明完成器方法返回的函數(shù)是一個(gè)恒等函數(shù),可以跳過。這種情況下,累加器對(duì)象將會(huì)直接用作歸約過程的最終結(jié)果。這也意味著,將累加器 A 不加檢查地轉(zhuǎn)換為結(jié)果 R 是安全的。
我們迄今開發(fā)的 ToListCollector 是 IDENTITY_FINISH 的,因?yàn)橛脕砝鄯e流中元素的List 已經(jīng)是我們要的最終結(jié)果,用不著進(jìn)一步轉(zhuǎn)換了,但它并不是 UNORDERED ,因?yàn)橛迷谟行蛄魃系臅r(shí)候,我們還是希望順序能夠保留在得到的 List 中。
最后,它是CONCURRENT 的,但我們剛才說過了,僅僅在背后的數(shù)據(jù)源無序時(shí)才會(huì)并行處理。
自定義Collector Demo
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {private void log(final String log) {System.out.println(Thread.currentThread().getName() + "-" + log);}@Overridepublic Supplier<List<T>> supplier() {log("supplier");return ArrayList::new;}@Overridepublic BiConsumer<List<T>, T> accumulator() {log("accumulator");return List::add;}@Overridepublic BinaryOperator<List<T>> combiner() {log("combiner");return (list1, list2) -> {list1.addAll(list2);return list1;};}@Overridepublic Function<List<T>, List<T>> finisher() {log("finisher");return t -> t;}@Overridepublic Set<Characteristics> characteristics() {log("characteristics");return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));}
測(cè)試下
總結(jié)
以上是生活随笔為你收集整理的Java 8 - 自定义Collector的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解分布式技术 - 缓存过期策略手写
- 下一篇: Java开发规范01 - 集合篇_Arr