java 8 stream reduce詳解和誤區


java 8 stream reduce詳解和誤區

簡介

Stream API提供了一些預定義的reduce操作,比如count(), max(), min(), sum()等。

如果我們需要自己寫reduce的邏輯,則可以使用reduce方法。

本文將會詳細分析一下reduce方法的使用,並給出具體的例子。

reduce詳解

Stream類中有三種reduce,分別接受1個參數,2個參數,和3個參數,首先來看一個參數的情況:

Optional reduce(BinaryOperator accumulator);

該方法接受一個BinaryOperator參數,BinaryOperator是一個@FunctionalInterface,需要實現方法:

R apply(T t, U u);

accumulator告訴reduce方法怎麼去累計stream中的數據。

舉個例子:

List<integer> intList = Arrays.asList(1,2,3); Optional<integer> result1=intList.stream().reduce(Integer::sum); log.info("{}",result1);/<integer>/<integer>

上面的例子輸出結果:

com.flydean.ReduceUsage - Optional[6]

一個參數的例子很簡單。這裡不再多說。

接下來我們再看一下兩個參數的例子:

T reduce(T identity, BinaryOperator accumulator);

這個方法接收兩個參數:identity和accumulator。多出了一個參數identity

也許在有些文章裡面有人告訴你identity是reduce的初始化值,可以隨便指定,如下所示:

Integer result2=intList.stream().reduce(100, Integer::sum); log.info("{}",result2);


上面的例子,我們計算的值是106。

如果我們將stream改成parallelStream:

Integer result3=intList.parallelStream().reduce(100, Integer::sum); log.info("{}",result3);

得出的結果就是306。

為什麼是306呢?因為在並行計算的時候,每個線程的初始累加值都是100,最後3個線程加出來的結果就是306。

並行計算和非並行計算的結果居然不一樣,這肯定不是JDK的問題,我們再看一下JDK中對identity的說明:

identity必須是accumulator函數的一個identity,也就是說必須滿足:對於所有的t,都必須滿足 accumulator.apply(identity, t) == t

所以這裡我們傳入100是不對的,因為sum(100+1)!= 1。

這裡sum方法的identity只能是0。

如果我們用0作為identity,則stream和parallelStream計算出的結果是一樣的。這就是identity的真正意圖。

下面再看一下三個參數的方法:

U reduce(U identity,

BiFunction accumulator,

BinaryOperator combiner);

和前面的方法不同的是,多了一個combiner,這個combiner用來合併多線程計算的結果。

同樣的,identity需要滿足combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

大家可能注意到了為什麼accumulator的類型是BiFunction而combiner的類型是BinaryOperator?

public interface BinaryOperator extends BiFunction

BinaryOperator是BiFunction的子接口。BiFunction中定義了要實現的apply方法。

其實reduce底層方法的實現只用到了apply方法,並沒有用到接口中其他的方法,所以我猜測這裡的不同只是為了簡單的區分。

總結

雖然reduce是一個很常用的方法,但是大家一定要遵循identity的規範,並不是所有的identity都是合適的。


分享到:


相關文章: