累积:轻松自定义Java收集器
Accumulative是針對Collector<T, A, R>的中間累積類型A提出的接口Collector<T, A, R>以使定義自定義Java Collector更加容易。
介紹
如果您曾經使用過Java Stream ,那么很可能使用了一些Collector ,例如:
- Collectors.toList
- Collectors.toMap
但是你有沒有使用過……
- 它使用另一個 Collector作為參數,例如: Collectors.collectingAndThen 。
- 其功能在Collector.of明確指定。
這篇文章是關于custom Collector的。
集電極
讓我們回想一下Collector合同的本質 (我的評論):
/** * @param <T> (input) element type * @param <A> (intermediate) mutable accumulation type (container) * @param <R> (output) result type */ public interface Collector<T, A, R> { Supplier<A> supplier(); // create a container BiConsumer<A, T> accumulator(); // add to the container BinaryOperator<A> combiner(); // combine two containers Function<A, R> finisher(); // get the final result from the container Set<Characteristics> characteristics(); // irrelevant here }上面的合同本質上是功能性的,這非常好! 這使我們可以使用任意累積類型( A )創建Collector ,例如:
- A : StringBuilder ( Collectors.joining )
- A : OptionalBox ( Collectors.reducing )
- A : long[] ( Collectors.averagingLong )
提案
在我提供任何理由之前,我將提出建議,因為它很簡短。 該提議的完整源代碼可以在GitHub上找到 。
累積接口
我建議將以下稱為Accumulative (名稱待討論)的接口添加到JDK:
public interface Accumulative<T, A extends Accumulative<T, A, R>, R> { void accumulate(T t); // target for Collector.accumulator() A combine(A other); // target for Collector.combiner() R finish(); // target for Collector.finisher() }與Collector相反,此接口本質上是面向對象的 ,實現該接口的類必須表示某種可變狀態 。
過載收集器
具有Accumulative ,我們可以添加以下Collector.of重載:
public static <T, A extends Accumulative<T, A, R>, R> Collector<T, ?, R> of( Supplier<A> supplier, Collector.Characteristics... characteristics) { return Collector.of(supplier, A::accumulate, A::combine, A::finish, characteristics); }普通開發者故事
在本部分中,我將展示該建議會對普通開發人員產生怎樣的影響,而一般開發人員僅了解 Collector API的基礎知識 。 如果您精通此API,請在繼續閱讀之前盡力想象您不知道。
例
讓我們重用我最近的文章中的示例(進一步簡化)。 假設我們有一個Stream :
interface IssueWiseText { int issueLength(); int textLength(); }并且我們需要計算問題覆蓋率 :
總發行時長
─────────────
總文字長度
此要求轉換為以下簽名:
Collector<IssueWiseText, ?, Double> toIssueCoverage();解
一般的開發人員可能會決定使用自定義累積類型A來解決此問題(不過其他解決方案也是可能的 )。 假設開發人員將其命名為CoverageContainer這樣:
- T : IssueWiseText
- A : CoverageContainer
- R : Double
在下面,我將展示這樣的開發人員如何實現CoverageContainer的結構 。
無累積結構
注意 :本節很長,目的是說明該過程對于沒有使用Collector的開發人員可能有多復雜 。 如果您已經意識到這一點,則可以跳過它
如果沒有Accumulative ,則開發人員將查看Collector.of ,并看到四個主要參數:
要處理Supplier <A> supplier ,開發人員應:
要處理BiConsumer <A, T> accumulator ,開發人員應:
void accumulate(IssueWiseText t)
處理BinaryOperator <A> combiner :
要處理Function <A, R> finisher :
這個漫長的過程導致:
class CoverageContainer { void accumulate(IssueWiseText t) { } CoverageContainer combine(CoverageContainer other) { } double issueCoverage() { } }開發人員可以定義toIssueCoverage() (必須以正確的順序提供參數):
Collector<IssueWiseText, ?, Double> toIssueCoverage() { return Collector.of( CoverageContainer:: new , CoverageContainer::accumulate, CoverageContainer::combine, CoverageContainer::finish ); }累積結構
現在, 使用 Accumulative ,開發人員將查看新的Collector.of重載,并且將僅看到一個主要參數:
和一個有界類型參數 :
- A extends Accumulative<T, A, R>
因此,開發人員將自然而然地開始- 實施 Accumulative<T, A, R>并第一次和最后一次解析T , A和R :
class CoverageContainer implements Accumulative<IssueWiseText, CoverageContainer, Double> { }此時,一個不錯的IDE會抱怨該類必須實現所有抽象方法。 而且,這是最美麗的部分 ,它將提供快速修復。 在IntelliJ中,您單擊“ Alt + Enter”→“實施方法”,然后…就完成了!
class CoverageContainer implements Accumulative<IssueWiseText, CoverageContainer, Double> { @Override public void accumulate(IssueWiseText issueWiseText) { ????} @Override public CoverageContainer combine(CoverageContainer other) { return null ; } @Override public Double finish() { return null ; } }因此,您不必擺弄類型,手動編寫任何內容或命名任何內容!
哦,是的-您仍然需要定義toIssueCoverage() ,但是現在很簡單:
Collector<IssueWiseText, ?, Double> toIssueCoverage() { return Collector.of(CoverageContainer:: new ); }那不是很好嗎?
實作
這里的實現無關緊要,因為這兩種情況( diff )幾乎相同。
基本原理
程序太復雜
我希望我已經演示了如何定義自定義Collector是一個挑戰。 我必須說,即使我總是不愿意定義一個。 但是,我也感覺到-有了Accumulative ,這種勉強就會消失,因為該過程將縮小為兩個步驟:
推動實施
JetBrains創造了“ 發展動力 ”,我想將其轉變為“實施動力”。
由于Collector是一個簡單的功能的設備中,這通常是沒有意義的(據我可以告訴)來實現它(也有例外 )。 但是,通過Google搜索“實施收集器”可以看到(約5000個結果)人們正在這樣做。
這很自然,因為要在Java中創建“自定義” TYPE ,通常會擴展/實現TYPE 。 實際上,即使是經驗豐富的開發人員(例如Java冠軍Tomasz Nurkiewicz )也可以做到這一點。
總結起來,人們感到有實現的動力 ,但在這種情況下,JDK沒有為他們提供實現的任何東西。 Accumulative可以填補這一空白……
相關例子
最后,我搜索了一些示例,這些示例可以輕松實現Accumulative 。
在OpenJDK(盡管這不是目標位置)中,我發現了兩個:
對堆棧溢出,雖然,我發現大量的: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 52 , 53 。
我還發現了一些基于數組的示例,可以將其重構為Accumulative以獲得更好的可讀性: a , b , c 。
命名
Accumulative不是最好的名字,主要是因為它是一個形容詞 。 但是,我選擇它是因為:
- 我希望名稱以A開頭(如<T, A, R> ),
- 我最好的候選人( Accumulator )已經被BiConsumer<A, T> accumulator() ,
- AccumulativeContainer似乎太長。
在OpenJDK中, A稱為:
- 可變結果容器
- 累積類型
- 容器
- 州
- 框
提示以下替代方法:
- AccumulatingBox
- AccumulationState
- Collector.Container
- MutableResultContainer
當然,如果這個想法被接受,這個名字將通過“傳統”的名字
摘要
在本文中,我建議向JDK添加Accumulative接口和新的Collector.of重載。 有了它們,開發人員將不再費勁地創建自定義Collector 。 取而代之的是,它只是成為“執行合同”和“引用構造函數”。
換句話說,該提案旨在降低進入“定制Collector世界的門檻 !
附錄
下面的可選閱讀。
解決方案示例:JDK 12+
在JDK 12+中,由于Collectors.teeing ( JDK-8209685 ),我們將toIssueCoverage()定義為組合的Collector 。
static Collector<IssueWiseText, ?, Double> toIssueCoverage() {return Collectors.teeing(Collectors.summingInt(IssueWiseText::issueLength),Collectors.summingInt(IssueWiseText::textLength),(totalIssueLength, totalTextLength) -> (double) totalIssueLength / totalTextLength); }上面的內容很簡潔,但是對于Collector API新手來說,可能很難遵循。
示例解決方案:JDK方法
另外, toIssueCoverage()可以定義為:
static Collector<IssueWiseText, ?, Double> toIssueCoverage() {return Collector.of(() -> new int[2],(a, t) -> { a[0] += t.issueLength(); a[1] += t.textLength(); },(a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },a -> (double) a[0] / a[1]); }我稱其為“ JDK方式”,因為某些Collector的實現與OpenJDK中的實現類似(例如Collector.averagingInt )。
但是,盡管這樣的簡潔代碼可能適用于OpenJDK,但由于可讀性高(這很低,我稱之為cryptic ),因此它肯定不適合業務邏輯。
翻譯自: https://www.javacodegeeks.com/2019/02/accumulative-custom-java-collectors.html
總結
以上是生活随笔為你收集整理的累积:轻松自定义Java收集器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云平台备案是什么(云平台备案)
- 下一篇: Java的精妙之处,包括基元和变量参数数