一文搞懂 Java8 reduce操作
什么是 reduce
Java8 中有兩大最為重要的改變,其一是 Lambda 表達式,另一個就是 Stream API 了。
Stream 是 Java8 中處理集合的關(guān)鍵抽象概念,它將數(shù)據(jù)源流化后,可以執(zhí)行非常復(fù)雜的查找、過濾和映射數(shù)據(jù)、排序、切片、聚合統(tǒng)計等操作。操作之后會產(chǎn)生一個新的流,而數(shù)據(jù)源則不會發(fā)生改變。
在使用 Stream 操作的過程中,往往有三個步驟,
1. 創(chuàng)建 Stream
從一個數(shù)據(jù)源(集合,數(shù)組)中,新建一個 Stream 流。
2. 中間操作
一個中間操作鏈,對 Stream 流的數(shù)據(jù)進行處理。比如查找、過濾、映射轉(zhuǎn)換等。
3. 終止操作
一個終止操作,執(zhí)行中間操作鏈,并產(chǎn)生結(jié)果。常用的終止操作有 forearch、collect、match、count、min、max、reduce 等。
其中本文主要講解的 reduct 操作就屬于是 Stream 流操作中的終止操作。
reduce 操作是一種通用的歸約操作,它可以實現(xiàn)從 Stream 中生成一個值,其生成的值不是隨意的,而是根據(jù)指定的計算模型。
比如終止操作中提到 count、min 和 max 方法,因為常用而被納入標(biāo)準(zhǔn)庫中。事實上這些方法都是一種 reduce 操作。
本文大綱如下,
圖片
reduce 操作三要素
為了方便大家理解 reduce (規(guī)約)操作,先給大家演示一下 reduce 操作的相關(guān)代碼示例,
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
.stream()
.reduce(0, (subtotal, element) -> subtotal + element);
assertThat(result).isEqualTo(21);
可以看到,我們的 reduce 操作接受了三個參數(shù),返回了一個 int 基本類型。
在 Stream API 中,提供了三個 reduct 操作方法,根據(jù)參數(shù)不同進行區(qū)分。
圖片
對應(yīng)上方代碼示例,也就是使用了接受兩個參數(shù)的 reduce 方法,但其實接受兩個參數(shù)的 reduce 方法的代碼邏輯是和接受三個參數(shù)的 reduce 方法是一致的。通過上方截圖可以看出。
所以這里,我就直接給大家介紹下 reduce 操作的三個參數(shù)分別有什么作用即可。
identiy 參數(shù)
identiy(初始值)是 reduce 操作的初始值,也就是當(dāng)元素集合為空時的默認(rèn)結(jié)果。對應(yīng)上方代碼示例,也就是說 reduce 操作的初始值是 0。
accumulator 參數(shù)
accumulator(累加器)是一個函數(shù),它接受兩個參數(shù),reduce 操作的部分元素和元素集合中的下一個元素。它返回一個新的部分元素。在這個例子中,累加器是一個 lambda 表達式,它將集合中兩個整數(shù)相加并返回一個整數(shù):(a, b) -> a + b。
combiner 參數(shù)
combiner(組合器)是一個函數(shù),它用于在 reduce 操作被并行化或者當(dāng)累加器的參數(shù)類型和實現(xiàn)類型不匹配時,將 reduce 操作的部分結(jié)果進行組合。在上面代碼示例中,我們不需要使用組合器,因為上面我們的 reduce 操作不需要并行,而且累加器的參數(shù)類型和實現(xiàn)類型都是 Integer。
為了方便大家理解 reduce 操作的內(nèi)部邏輯,我給大家繪制了上面代碼示例的執(zhí)行示意圖,如下,
如何使用 reduce 操作
為了更好地理解初始值,累加器和組合器的功能,讓我們看一些基本的例子。
使用 reduce 查詢整數(shù)集合的最小值
// 創(chuàng)建一個整數(shù)集合
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 找出集合中的最小值
Integer min = numbers.stream().reduce((integer, integer2) -> {
if (integer < integer2) {
return integer;
} else {
return integer2;
}
}).get();
// 輸出結(jié)果
System.out.println(min); // 1
在這個例子中,我們使用了一個參數(shù)的 reduce 操作,它接受一個累加器函數(shù)。累加器函數(shù)會返回集合兩個元素中,較小的元素。
最終我們就可以找出集合中最小值 1。
使用 reduce 操作拼接字符串列表
我們可以對一個字符串列表使用 reduce 操作,將它們拼接成一個單一的字符串:
// 創(chuàng)建一個字符串列表
List<String> letters = Arrays.asList ("a", "b", "c", "d", "e");
// 使用 reduce 操作拼接字符串列表
String result = letters
.stream ()
.reduce ("", (partialString, element) -> partialString + element);
// 輸出結(jié)果
System.out.println (result); // abcde
在這個例子中,我們將初始值設(shè)為 "",累加器函數(shù)設(shè)為 (a, b) -> a + b,它表示將兩個字符串拼接起來。
我們可以看到,reduce 操作將累加器函數(shù)反復(fù)應(yīng)用到列表中的每個元素上,得到最終的結(jié)果 abcde。
使用并行流計算整數(shù)列表的總和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,6);
// 使用并行流和 reduce() 方法計算整數(shù)列表的總和
int result = numbers.parallelStream()
.reduce(0, (a, b) -> a + b, Integer::sum);
// 輸出結(jié)果
System.out.println(result); // 21
在這個例子中,我們使用 parallelStream() 方法將列表轉(zhuǎn)換為并行流,再使用 reduce() 方法對整數(shù)列表進行 reduce 操作,并使用 Integer::sum 作為合并函數(shù) combiner,將并行計算的結(jié)果合并。
使用并行流的好處能夠充分利用多核 CPU 的優(yōu)勢,使用多線程加快對集合數(shù)據(jù)的處理速度。
不過并行流也不是任何時候都可以使用的,并行流執(zhí)行過程中實際按照多線程執(zhí)行,多線程編程有的問題,并行流都有。
比如多線程的線程安全,執(zhí)行順序等問題,并行流都是有的。這一點需要大家注意。