Java8之Stream流(三)縮減操作

weixin_34279579發表於2018-07-23

Java8之Stream流(一)基礎體驗
Java8之Stream流(二)關鍵知識點
Java8之Stream流(四)並行流
Java8之Stream流(五)對映流
Java8之Stream流(六)收集
Java8之Stream流(七)流與迭代器

和前面兩篇文章一起服用,效果會更佳。通過對流API的基礎體驗Demo和關鍵知識點的講解 ,相信大家對流API都有一定的認識了,但是流API強大的功能,可不僅僅像前面兩篇文章中說的那樣簡單,大家應該注意到,在第二篇中,我對Stream介面進行介紹的時候,並沒有把他的全部方法都進行了解析說明。沒錯,從這一篇開始,那些還沒有講解的方法,很可能就開始變成我們的主角了,大家從題目上面應該知道了,本期我們要講的是流API的縮減操作。

何為縮減操作?

我們先考慮一下min()和max(),這兩個方法我們在第一篇和第二篇中均有提到,其中min()是返回流中的最小值,而max()返回流中最大值,前提是他們存在。他們之間的特點是什麼?①都返回了一個值②由一可知,他們是終端操作。如果我們用流API的術語來形容前面這兩種特性的結合體的話,它們代表了縮減操作。因為每個縮減操作都把一個流縮減為一個值,好比最大值,最小值。當然流API,把min()和max(),count()這些操作稱為特例縮減。即然說到了特例,肯定就有泛化這種概念了,他就是reduce()方法了,其實第二篇當中,他已經出現過了,只是當時我沒有去強調他。

public interface Stream<T> extends BaseStream<T, Stream<T>> {
//、、、忽略其他無關緊要的元素
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
          BiFunction<U, ? super T, U> accumulator,
          BinaryOperator<U> combiner);
}

Stream介面定義了三個版本的reduce(),我們先使用前面兩個,

T reduce(T identity, BinaryOperator<T> accumulator);//1
Optional<T> reduce(BinaryOperator<T> accumulator);//2

第一個版本返回的是一個T型別的物件,T代表的是流中的元素型別!第二個版本是返回一個Optional型別物件。對於這兩種形式,accumulator是一個操作兩個值並得到結果的函式。在第一個版本當中,identity是這樣一個值,對於涉及identity和流中任意的累積操作,得到的結果就是元素自身,沒有任何改變。比如,如果是加法,他就是0,如果是乘法他就是1。

其中的accumulator是一個BinaryOperator<T>的型別,他是java.util.function包中宣告的函式式介面,它擴充套件了BiFunction函式式介面.

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
   R apply(T t, U u);//notice
}

BiFunction介面中的apply()方法的原型在//notice。其中R指定了結果的型別,T,U分別是第一引數的型別和第二個引數的型別,因此apply()對他的兩個運算元(t,u)應用到同一個函式上,並返回結果,而對BinaryOperator<T>來說,他在擴充套件 BiFunction時,指定了所有的型別引數都是相同的T,因此對於BinaryOperator<T>函式式介面的apply來說,他也就變成了 T apply(T t, T u),此外,還有一個需要注意的地方是,在應用reduce()時,apply()的第一個引數t,包含的是一個結果,u包含的是下一個元素。在第一次呼叫時,將取決於使用reduce()的版本,t可能是單位值,或者是前一個元素。

縮減操作的三個約束

  • 無狀態
  • 不干預
  • 關聯性

無狀態,這裡可不是LOL的那個無狀態,畢竟他退役了。相信讀過第二篇文章的同學已經很容易理解了,簡單來說無狀態就是每個元素都被單獨地處理,他和流中的其它元素是沒有任何依賴關係的。不干預是指運算元不會改變資料來源。最後,操作必須具有關聯性,這裡的關聯性是指標準的數學含義,即,給定一個關聯運算子,在一系列操作中使用該運算子,先處理哪一對運算元是無關緊要的。比如,(1 * 2) * 3 <===> 1 * (2 * 3)。其中關聯性,在並行流中,是至關重要的。下面我用一個簡單的例子帶著大家實戰一下泛化縮減操作reduce()的使用。

public class Main {

    public static void main(String[] args) {
        learnStream();
    }


    private static void learnStream() {
        List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
        lists.add(4);
        lists.add(5);
        lists.add(6);

        Optional<Integer> sum = lists.stream().reduce((a, b) -> a + b);
        if (sum.isPresent()) System.out.println("list的總和為:" + sum.get());//21
        //<====> lists.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);

        Integer sum2 = lists.stream().reduce(0, (a, b) -> a + b);//21
        System.out.println("list的總和為:" + sum2);

        Optional<Integer> product = lists.stream().reduce((a, b) -> a * b);
        if (product.isPresent()) System.out.println("list的積為:" + product.get());//720

        Integer product2 = lists.stream().reduce(1, (a, b) -> a * b);
        System.out.println("list的積為:" + product2);//720
    }
}

這個Demo主要是計算了一個list裡面的總和,積的操作,大家可以和傳統的算總和,積的方法進行對照,比一比衡量一下就有自己的答案了。但是如果你以為流API僅此而已,那你就錯了。越是後面的東西,就越裝B,我在剛知道他們的時候,反正是被嚇了一跳的,但這些都是後話了,現在我們來詳解一下Demo,並給出擴充套件的方向:我們這個例子主要是用了lambda表示式對list進行了求和,求積,對於第一個版本為說,求和的時候,identity的值為0,求積的時候它的值為1,強烈建議你們自己感受一下identity的變化對整個結果的變化產生什麼 的影響,改變一下identity的值,再執行一下,你就有結果了,另一個擴充套件點是:

 Integer product3 = lists.stream().reduce(1, (a, b) -> {
            if (b % 2 == 0) return a * b; else return a;//這裡你可以為所欲為!
 });
 System.out.println("list的偶數的積為:" + product3);//48

小結一下

對於流的縮減操作來說,主要要知道,他只返回一個值,並且它是一個終端操作,然後還有的就是要知道縮減操作的三個約束了,其實最重要的就是無狀態性和關聯性了.這一小節要說的,也就這麼多了,應該很容易就把他收到自己的技能樹上面了。

相關文章