簡介
Stream API提供了一些預定義的reduce操作,比如count(), max(), min(), sum()等。
如果我們需要自己寫reduce的邏輯,則可以使用reduce方法。
本文將會詳細分析一下reduce方法的使用,並給出具體的例子。
reduce詳解
Stream類中有三種reduce,分別接受1個參數,2個參數,和3個參數,首先來看一個參數的情況:
Optional
該方法接受一個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
這個方法接收兩個參數: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
BinaryOperator是BiFunction的子接口。BiFunction中定義了要實現的apply方法。
其實reduce底層方法的實現只用到了apply方法,並沒有用到接口中其他的方法,所以我猜測這裡的不同只是為了簡單的區分。
總結
雖然reduce是一個很常用的方法,但是大家一定要遵循identity的規範,並不是所有的identity都是合適的。
閱讀更多 Java頂級架構師 的文章