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

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

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

注意:本文所講述的「流」不是 XXXInputStreamXXXOutputStreamapi

預備知識: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。oracle

lambda 表達式

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

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

其中參數列表內,每一個參數的類型是可選的,若是參數列表內沒有參數,或參數不止一個時,須要用 () 進行佔位。ide

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

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

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

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());

目前我遇到的操做大體就這些,以後遇到實際的例子會繼續添加到本文。

相關文章
相關標籤/搜索