Java8 流式 API(`java.util.stream`)


熟悉 ES6 的開發者,肯定對數組的一些方法不是很陌生:mapfilter 等。在對一組對象進行統一操作時,利用這些方法寫出來的代碼比常規的迭代代碼更加的簡練。在 C♯ 中,有 LINQ 來實現。那么在 Java 中有這樣的操作嗎?答案是有的,Java8 中引入了大量新特性,其中一個就是 Java 的流式 API。

在 Java 8 中,流(Stream)與迭代器類似,都是用來對集合內的元素進行某些操作。它們之間最大的差別,是對迭代器的每個操作都會即時生效,而對流的操作則不是這樣。流的操作有兩種,中間操作和終止操作。對於中間操作並不會立即執行,只有當終止操作執行時,前面的中間操作才會一並執行(稱之為惰性求值)。對於某些復雜操作,流的效率會比傳統的迭代器要高。

注意:本文所講述的“流”不是 XXXInputStreamXXXOutputStream

預備知識:lambda 表達式、Functional Interface

Functional Interface

在 Java8 中,新加入了一個注解:@FunctionalInterface,用於標記一個接口是函數接口(即有且只有一個方法(不包括那些有默認實現的方法和標記為 static 的方法))。一個典型的例子就是 Java 中用於多線程的 Runnable 接口:

@FunctionalInterface
public interface Runnable {
    void run();
}

另外一個例子來自於 Java8 中預定義的一些接口(位於 java.util.function 包下)

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

如果自己定義函數式接口,@FunctionalInterface 注解是可選的,只要接口內除靜態方法和有默認實現的方法之外有且只有一個方法,那么這個接口就被認為是 Functional Interface。

lambda 表達式

lambda 表達式是 Java 8 中新引進的語法糖,主要作用是快速定義一個函數(或一個方法)。其基本語法如下:

(參數列表) -> { 表達式內容 }

其中參數列表內,每個參數的類型是可選的,如果參數列表內沒有參數,或參數不止一個時,需要用 () 進行占位。

lambda 表達式的主要作用,就是用於簡化代碼。熟悉 Java GUI 的讀者知道,之前要給一個控件添加事件響應的時候,我們通常是使用匿名內部類進行處理:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 這里處理事件響應的代碼
    }
});

顯然,這種寫法是比較麻煩的,我們觀察上面的代碼,可以看到 ActionListener 中只有一個方法。控件的 addActionListener 實際上接受的是一個方法作為參數,事件發生時調用該方法作為響應。lambda 表達式的作用就是用於快速定義方法,於是可以對上面的方法改寫成如下形式

button.addActionListener(e -> {
    // 處理事件響應
})

可以看到,引入 lambda 表達式后,整個方法都變得十分簡潔。這就是 lambda 表達式的作用。

基本使用

打開流

可以用如下方法打開一個 Stream

  1. 使用 Collection 子類的 stream()(串行流)或 parallelStream()
  2. 使用 Arrays.stream() 方法為數組創建一個流
  3. 使用 Stream.of() 方法創建流
  4. 使用 Stream.iterate() 方法創建流
  5. 使用 Stream.generate() 方法創建流

其中前三種創建的流是有限流(里面的元素數量是有限個,因為創建該流的集合內元素數量也是有限的),后兩種創建的流是無限流(里面的元素是由傳入的參數進行生成的,具體可參閱 API 文檔

對流進行操作

前文說過,流的操作有兩種:中間操作和終止操作。辨別這兩種操作的方法很簡單:觀察這些操作的返回值。如果方法的返回值是 Stream<T> 說明操作返回的是流自身,可以進行下一步操作,這一操作為中間操作,反之則為終止操作,終止操作結束后流即失效,想再次使用則需要創建新的流。

下面列舉一些(至少我比較經常用到的)一些流的操作

操作 描述
<R> Stream<R> map(Function<? super T, ? extends R> mapper) 將流里面的每個元素通過 mapper 轉換為另一個元素,並生成一個對應類型的流
Stream<T> filter(Predicate<? super T> predicate) 從流里挑出所有符合 predicate 條件的所有元素,並放入一個新的流中
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 將流里面的每個元素“展開”,形成一個新的流(通常用於展開嵌套的 List 或數組(把矩陣轉換為數組之類的))
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
常見的應用場景:求和。簡單來說就是對流內的每個元素進行一次操作,最后得到一個結果
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
<R, A> collect<Collector<? super T, A, R> collector
常見的應用場景:把流中的元素收集到一個 List
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
判斷流中是否所有元素(存在元素)滿足 predicate 判斷條件

以上僅展示了部分常用操作,其余操作可參見 Stream 類的 API 文檔,另外不要被 API 的參數嚇到。這些參數實際上大部分是來自於 java.util.function 的接口,且均為前文所說的 Functional Interface,所以實際使用時,我們都是傳遞 lambda 表達式給參數。

舉例

對於選擇題來說,其選項可以由以下結構表示

class Question {
    String body;
    List<Option> options;
    
    // 省略 getter/setter
}

class Option {
    String answer;
    boolean right;
    
    // 省略 getter/setter
}

假如我們有一個選擇題的題庫,要往里面添加一道選擇題,要求在插入前要進行判斷,說每個題目必須有至少一個正確答案,則可以這樣寫:

boolean isValidQuestion(Question question) {
    return question.getOptions.stream().anyMatch(option -> option.isRight());
}

再舉一個例子,已知 Date 類有一個 toInstant() 方法可以將 Date 轉化為 Instant,現有一個 List<Date> 的變量 dates,想將其轉化為 List<Instant> 類型,可以這樣寫:

dates.stream().map(Date::toInstant).collect(Collectors.toList());

目前我遇到的操作大致就這些,之后遇到實際的例子會繼續添加到本文。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM