累积:轻松自定义Java收集器
Accumulative是針對Collector<T, A, R>的中間累積類型A提出的接口Collector<T, A, R>以使定義自定義Java Collector更加容易。
介紹
如果您曾經(jīng)使用過Java Stream ,那么很可能使用了一些Collector ,例如:
- Collectors.toList
- Collectors.toMap
但是你有沒有使用過……
- 它使用另一個(gè) Collector作為參數(shù),例如: Collectors.collectingAndThen 。
- 其功能在Collector.of明確指定。
這篇文章是關(guān)于custom Collector的。
集電極
讓我們回想一下Collector合同的本質(zhì) (我的評論):
/** * @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 }上面的合同本質(zhì)上是功能性的,這非常好! 這使我們可以使用任意累積類型( A )創(chuàng)建Collector ,例如:
- A : StringBuilder ( Collectors.joining )
- A : OptionalBox ( Collectors.reducing )
- A : long[] ( Collectors.averagingLong )
提案
在我提供任何理由之前,我將提出建議,因?yàn)樗芎喍獭?該提議的完整源代碼可以在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相反,此接口本質(zhì)上是面向?qū)ο蟮?/strong> ,實(shí)現(xiàn)該接口的類必須表示某種可變狀態(tài) 。
過載收集器
具有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); }普通開發(fā)者故事
在本部分中,我將展示該建議會對普通開發(fā)人員產(chǎn)生怎樣的影響,而一般開發(fā)人員僅了解 Collector API的基礎(chǔ)知識 。 如果您精通此API,請?jiān)诶^續(xù)閱讀之前盡力想象您不知道。
例
讓我們重用我最近的文章中的示例(進(jìn)一步簡化)。 假設(shè)我們有一個(gè)Stream :
interface IssueWiseText { int issueLength(); int textLength(); }并且我們需要計(jì)算問題覆蓋率 :
總發(fā)行時(shí)長
─────────────
總文字長度
此要求轉(zhuǎn)換為以下簽名:
Collector<IssueWiseText, ?, Double> toIssueCoverage();解
一般的開發(fā)人員可能會決定使用自定義累積類型A來解決此問題(不過其他解決方案也是可能的 )。 假設(shè)開發(fā)人員將其命名為CoverageContainer這樣:
- T : IssueWiseText
- A : CoverageContainer
- R : Double
在下面,我將展示這樣的開發(fā)人員如何實(shí)現(xiàn)CoverageContainer的結(jié)構(gòu) 。
無累積結(jié)構(gòu)
注意 :本節(jié)很長,目的是說明該過程對于沒有使用Collector的開發(fā)人員可能有多復(fù)雜 。 如果您已經(jīng)意識到這一點(diǎn),則可以跳過它
如果沒有Accumulative ,則開發(fā)人員將查看Collector.of ,并看到四個(gè)主要參數(shù):
要處理Supplier <A> supplier ,開發(fā)人員應(yīng):
要處理BiConsumer <A, T> accumulator ,開發(fā)人員應(yīng):
void accumulate(IssueWiseText t)
處理BinaryOperator <A> combiner :
要處理Function <A, R> finisher :
這個(gè)漫長的過程導(dǎo)致:
class CoverageContainer { void accumulate(IssueWiseText t) { } CoverageContainer combine(CoverageContainer other) { } double issueCoverage() { } }開發(fā)人員可以定義toIssueCoverage() (必須以正確的順序提供參數(shù)):
Collector<IssueWiseText, ?, Double> toIssueCoverage() { return Collector.of( CoverageContainer:: new , CoverageContainer::accumulate, CoverageContainer::combine, CoverageContainer::finish ); }累積結(jié)構(gòu)
現(xiàn)在, 使用 Accumulative ,開發(fā)人員將查看新的Collector.of重載,并且將僅看到一個(gè)主要參數(shù):
和一個(gè)有界類型參數(shù) :
- A extends Accumulative<T, A, R>
因此,開發(fā)人員將自然而然地開始- 實(shí)施 Accumulative<T, A, R>并第一次和最后一次解析T , A和R :
class CoverageContainer implements Accumulative<IssueWiseText, CoverageContainer, Double> { }此時(shí),一個(gè)不錯(cuò)的IDE會抱怨該類必須實(shí)現(xiàn)所有抽象方法。 而且,這是最美麗的部分 ,它將提供快速修復(fù)。 在IntelliJ中,您單擊“ Alt + Enter”→“實(shí)施方法”,然后…就完成了!
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 ; } }因此,您不必?cái)[弄類型,手動(dòng)編寫任何內(nèi)容或命名任何內(nèi)容!
哦,是的-您仍然需要定義toIssueCoverage() ,但是現(xiàn)在很簡單:
Collector<IssueWiseText, ?, Double> toIssueCoverage() { return Collector.of(CoverageContainer:: new ); }那不是很好嗎?
實(shí)作
這里的實(shí)現(xiàn)無關(guān)緊要,因?yàn)檫@兩種情況( diff )幾乎相同。
基本原理
程序太復(fù)雜
我希望我已經(jīng)演示了如何定義自定義Collector是一個(gè)挑戰(zhàn)。 我必須說,即使我總是不愿意定義一個(gè)。 但是,我也感覺到-有了Accumulative ,這種勉強(qiáng)就會消失,因?yàn)樵撨^程將縮小為兩個(gè)步驟:
推動(dòng)實(shí)施
JetBrains創(chuàng)造了“ 發(fā)展動(dòng)力 ”,我想將其轉(zhuǎn)變?yōu)椤皩?shí)施動(dòng)力”。
由于Collector是一個(gè)簡單的功能的設(shè)備中,這通常是沒有意義的(據(jù)我可以告訴)來實(shí)現(xiàn)它(也有例外 )。 但是,通過Google搜索“實(shí)施收集器”可以看到(約5000個(gè)結(jié)果)人們正在這樣做。
這很自然,因?yàn)橐贘ava中創(chuàng)建“自定義” TYPE ,通常會擴(kuò)展/實(shí)現(xiàn)TYPE 。 實(shí)際上,即使是經(jīng)驗(yàn)豐富的開發(fā)人員(例如Java冠軍Tomasz Nurkiewicz )也可以做到這一點(diǎn)。
總結(jié)起來,人們感到有實(shí)現(xiàn)的動(dòng)力 ,但在這種情況下,JDK沒有為他們提供實(shí)現(xiàn)的任何東西。 Accumulative可以填補(bǔ)這一空白……
相關(guān)例子
最后,我搜索了一些示例,這些示例可以輕松實(shí)現(xiàn)Accumulative 。
在OpenJDK(盡管這不是目標(biāo)位置)中,我發(fā)現(xiàn)了兩個(gè):
對堆棧溢出,雖然,我發(fā)現(xiàn)大量的: 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 。
我還發(fā)現(xiàn)了一些基于數(shù)組的示例,可以將其重構(gòu)為Accumulative以獲得更好的可讀性: a , b , c 。
命名
Accumulative不是最好的名字,主要是因?yàn)樗且粋€(gè)形容詞 。 但是,我選擇它是因?yàn)?#xff1a;
- 我希望名稱以A開頭(如<T, A, R> ),
- 我最好的候選人( Accumulator )已經(jīng)被BiConsumer<A, T> accumulator() ,
- AccumulativeContainer似乎太長。
在OpenJDK中, A稱為:
- 可變結(jié)果容器
- 累積類型
- 容器
- 州
- 框
提示以下替代方法:
- AccumulatingBox
- AccumulationState
- Collector.Container
- MutableResultContainer
當(dāng)然,如果這個(gè)想法被接受,這個(gè)名字將通過“傳統(tǒng)”的名字
摘要
在本文中,我建議向JDK添加Accumulative接口和新的Collector.of重載。 有了它們,開發(fā)人員將不再費(fèi)勁地創(chuàng)建自定義Collector 。 取而代之的是,它只是成為“執(zhí)行合同”和“引用構(gòu)造函數(shù)”。
換句話說,該提案旨在降低進(jìn)入“定制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); }上面的內(nèi)容很簡潔,但是對于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方式”,因?yàn)槟承〤ollector的實(shí)現(xiàn)與OpenJDK中的實(shí)現(xiàn)類似(例如Collector.averagingInt )。
但是,盡管這樣的簡潔代碼可能適用于OpenJDK,但由于可讀性高(這很低,我稱之為cryptic ),因此它肯定不適合業(yè)務(wù)邏輯。
翻譯自: https://www.javacodegeeks.com/2019/02/accumulative-custom-java-collectors.html
總結(jié)
以上是生活随笔為你收集整理的累积:轻松自定义Java收集器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云平台备案是什么(云平台备案)
- 下一篇: jdk8 cms g1gc_G1 vs