java stream api中的reduce方法使用
java stream api是對(duì)函數(shù)式編程的支持姆另,雖然stream api和c# linq比起來就是拖拉機(jī)和法拉利的區(qū)別,不過勉強(qiáng)能用坟乾,總比沒有強(qiáng)迹辐。
stream api的reduce方法用于對(duì)stream中元素進(jìn)行聚合求值,最常見的用法就是將stream中一連串的值合成為單個(gè)值甚侣,比如為一個(gè)包含一系列數(shù)值的數(shù)組求和明吩。
reduce方法有三個(gè)重載的方法,方法簽名如下
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
第一個(gè)簽名方法接受一個(gè)BinaryOperator類型的lambada表達(dá)式殷费, 常規(guī)應(yīng)用方法如下
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce((a,b) -> a + b ).get();
System.out.println(result);
代碼實(shí)現(xiàn)了對(duì)numList中的元素累加印荔。lambada表達(dá)式的a參數(shù)是表達(dá)式的執(zhí)行結(jié)果的緩存,也就是表達(dá)式這一次的執(zhí)行結(jié)果會(huì)被作為下一次執(zhí)行的參數(shù)详羡,而第二個(gè)參數(shù)b則是依次為stream中每個(gè)元素仍律。如果表達(dá)式是第一次被執(zhí)行,a則是stream中的第一個(gè)元素殷绍。
int result = numList.stream().reduce((a,b) -> {
System.out.println("a=" + a + ",b=" + b);
return a + b;
} ).get();
在表達(dá)式中假如打印參數(shù)的代碼染苛,打印出來的內(nèi)容如下
a=1,b=2
a=3,b=3
a=6,b=4
a=10,b=5
表達(dá)式被調(diào)用了4次鹊漠, 第一次a和b分別為stream的第一和第二個(gè)元素主到,因?yàn)榈谝淮螞]有中間結(jié)果可以傳遞, 所以 reduce方法實(shí)現(xiàn)為直接將第一個(gè)元素作為中間結(jié)果傳遞躯概。
第二個(gè)簽名的實(shí)現(xiàn)
T reduce(T identity, BinaryOperator<T> accumulator);
與第一個(gè)簽名的實(shí)現(xiàn)的唯一區(qū)別是它首次執(zhí)行時(shí)表達(dá)式第一次參數(shù)并不是stream的第一個(gè)元素登钥,而是通過簽名的第一個(gè)參數(shù)identity來指定。我們來通過這個(gè)簽名對(duì)之前的求和代碼進(jìn)行改進(jìn)
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce(0,(a,b) -> a + b );
System.out.println(result);
其實(shí)這兩種實(shí)現(xiàn)幾乎差別娶靡,第一種比第一種僅僅多了一個(gè)字定義初始值罷了牧牢。 此外,因?yàn)榇嬖趕tream為空的情況,所以第一種實(shí)現(xiàn)并不直接方法計(jì)算的結(jié)果塔鳍,而是將計(jì)算結(jié)果用Optional來包裝伯铣,我們可以通過它的get方法獲得一個(gè)Integer類型的結(jié)果,而Integer允許null轮纫。第二種實(shí)現(xiàn)因?yàn)樵试S指定初始值腔寡,因此即使stream為空,也不會(huì)出現(xiàn)返回結(jié)果為null的情況掌唾,當(dāng)stream為空放前,reduce為直接把初始值返回。
第三種簽名的用法相較前兩種稍顯復(fù)雜糯彬,猶豫前兩種實(shí)現(xiàn)有一個(gè)缺陷凭语,它們的計(jì)算結(jié)果必須和stream中的元素類型相同,如上面的代碼示例撩扒,stream中的類型為int似扔,那么計(jì)算結(jié)果也必須為int,這導(dǎo)致了靈活性的不足搓谆,甚至無法完成某些任務(wù)虫几, 比入我們咬對(duì)一個(gè)一系列int值求和,但是求和的結(jié)果用一個(gè)int類型已經(jīng)放不下挽拔,必須升級(jí)為long類型辆脸,此實(shí)第三簽名就能發(fā)揮價(jià)值了,它不將執(zhí)行結(jié)果與stream中元素的類型綁死螃诅。
List<Integer> numList = Arrays.asList(Integer.MAX_VALUE,Integer.MAX_VALUE);
long result = numList.stream().reduce(0L,(a,b) -> a + b, (a,b)-> 0L );
System.out.println(result);
如上代碼所示啡氢,它能見int類型的列表合并成long類型的結(jié)果。
當(dāng)然這只是其中一種應(yīng)用罷了术裸,猶豫拜托了類型的限制我們還可以通過他來靈活的完成許多任務(wù)倘是,比入將一個(gè)int類型的ArrayList轉(zhuǎn)換成一個(gè)String類型的ArrayList
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6);
ArrayList<String> result = numList.stream().reduce(new ArrayList<String>(), (a, b) -> {
a.add("element-" + Integer.toString(b));
return a;
}, (a, b) -> null);
System.out.println(result);
執(zhí)行結(jié)果為
[element-1, element-2, element-3, element-4, element-5, element-6]
這個(gè)示例顯得有點(diǎn)雞肋,一點(diǎn)不實(shí)用袭艺,不過在這里我們的主要目的是說明代碼能達(dá)到什么樣的效果搀崭,因此代碼示例也不必取自實(shí)際的應(yīng)用場(chǎng)景。
從上面兩個(gè)示例可以看出第三個(gè)reduce比前面兩個(gè)強(qiáng)大的多猾编,它的功能已經(jīng)完全覆蓋前面兩個(gè)的實(shí)現(xiàn)瘤睹,如果我們不考慮代碼的簡(jiǎn)潔性,甚至可以拋棄前面兩個(gè)答倡。
另外轰传,還需要注意的是這個(gè)reduce的簽名還包含第三個(gè)參數(shù),一個(gè)BinaryOperator<U>類型的表達(dá)式瘪撇。在常規(guī)情況下我們可以忽略這個(gè)參數(shù)获茬,敷衍了事的隨便指定一個(gè)表達(dá)式即可港庄,目的是為了通過編譯器的檢查炊苫,因?yàn)樵诔R?guī)的stream中它并不會(huì)被執(zhí)行到忿晕,然而臂拓, 雖然此表達(dá)式形同虛設(shè)氧敢,可是我們也不是把它設(shè)置為null茬故,否者還是會(huì)報(bào)錯(cuò)第步。 在并行stream中煞檩,此表達(dá)式則會(huì)被執(zhí)行到定鸟,在這里我們不進(jìn)行講解稿存,因?yàn)槲易约阂矝]用過笨篷。
numList.parallelStream()
可獲得并行stream