Java8 歸約 reduce
本節將看到如何把一個流中的元素組合起來,使用reduce操作來表達更復雜的查詢,比如“計算菜單中的總的卡路里”或“菜單中卡路里最高的菜時哪一個”。此類查詢需要將流中所有元素反覆結合起來,得到一個值,比如一個Integer。這樣的查詢可以被歸類爲歸約操作(將流歸約成一個值)。用函數式編程語言的術語來說,這稱爲摺疊(fold),因爲你可以將一個操作看成一張長長的紙(你的流)反覆摺疊成一個小方塊,而這就是摺疊操作的結果。
元素求和
在我們研究如何使用reduce方法之前,先來看看如何使用for-each循環來對數字列表中的元素求和:
Int sum = 0;
for (int x : numbers)
Sum += x;
numbers中的每一個元素都用加法運算符反覆迭代來得到結果。通過反覆使用加法,你把一個數字列表歸約成了一個數字。這段代碼中有2個參數:
1.總和變量的初始值,在這裏是0;
2.將列表中所有元素結合在一起的操作,在這裏是+。
要是還能把所有的數字相乘,而不必去複製粘貼這段代碼,豈不是很好?這正是reduce操作的用武之地,它對這種重複應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和:
Int sum = numbers.stream().reduce(0, (a,b) -> a+b);
reduce 接受2個參數:
- 一個初始值,這裏是0:
- 一個BinaryOperator<T>來將兩個元素結合起來產生一個新值,這裏我們用的是lambda(a, b) -> a+b。
你也很容易把所有的元素相乘,只需要將另一個Lambda:(a, b) -> a*b
傳遞給reduce操作就可以了:
int product = numbers.stream().reduce(1, (a,b) -> a*b);
下圖展示了reduce操作時如何作用於一個流的:Lambda反覆結合每個元素,直到流被歸約成一個值。
讓我們深入研究一下reduce操作時如何對一個數字流求和的。首先,0作爲Lambda(a)的第一個參數,從流中獲取4作爲第二個參數(b)。0+4得到4,它成了新的累積值。然後再用累積值和流中下一個元素5調用Lambda,產生新的累積值9。接下來,再用累積值和下一個元素3調用Lambda,得到12。最後,用12和流中最後一個元素9調用Lambda,得到最終結果12。
你可以使用方法引用讓着段代碼更簡潔。在java8中,Integer類現在有了一個靜態的sum方法來對兩個數求和,這恰好時我們想要的,用不着反覆用Lambda寫同一段代碼了:
int sum = numbers.stream().reduce(Integer::sum);
無初始值
reduce 還有一個重載的變體,它不接受初始值,但是會返回一個Optional對象:
Optional<Integer> sum = numbers.stream().reduce((a,b) -> a+b);
爲什麼它返回一個Optional<Integer>呢?考慮流中沒有任何元素的情況。reduce操作無法返回其和,因爲它沒有初始值。這就是爲什麼結果被包裹在一個Optional對象裏,以表明和可能不存在。現在看看reduce還能做什麼。
如果對於有無初始值不太明白,可以查看源碼:
這是有初始值的源碼:
這是沒有初始值的源碼:
參考書籍:Java8實戰