引入流

流是什么

它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地并行處理。下面的討論,將會使用到這樣一個例子:一個 menu,它只是一個列表:

List<Dish> menu = Arrays.asList(
                  new Dish("pork", false, 800, Dish.Type.MEAT),
                  new Dish("beef", false, 700, Dish.Type.MEAT),
                  new Dish("chicken", false, 400, Dish.Type.MEAT),
                  new Dish("french fries", true, 530, Dish.Type.OTHER),
                  new Dish("rice", true, 350, Dish.Type.OTHER),
                  new Dish("season fruit", true, 120, Dish.Type.OTHER),
                  new Dish("pizza", true, 550, Dish.Type.OTHER),
                  new Dish("prawns", false, 300, Dish.Type.FISH),
                  new Dish("salmon", false, 450, Dish.Type.FISH)
          );

Dish 類的定義是:

 public class Dish {
      private final String name;
      private final boolean vegetarian;
      private final int calories;
      private final Type type;

      public Dish(String name, boolean vegetarian, int calories, Type type) {
          this.name = name;
          this.vegetarian = vegetarian;
          this.calories = calories;
          this.type = type;
      }

      public String getName() {
          return name;
      }

      public boolean isVegetarian() {
          return vegetarian;
      }

      public int getCalories() {
          return calories;
      }

      public Type getType() {
          return type;
      }

      @Override
      public String toString() {
          return name;
      }

      public enum Type { MEAT, FISH, OTHER }
  }

流簡介

Java 8中的集合支持一個新的stream方法,它會返回一個流(接口定義在java.util.stream.Stream 里)。

那么,流到底是什么呢?簡短的定義就是“從支持數據處理操作的源生成的元素序列”。

  • 元素序列 —— 就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組有序值。因為集合是數據結構,所以它的主要目的是以特定的時間/空間復雜度存儲和訪問元素(如 ArrayList 與 LinkedList)。但流的目的在于表達計算,比如前面見到的 filter、sorted 和 map。集合講的是數據,流講的是計算。

  • 源 —— 流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。

  • 數據處理操作 —— 流的數據處理功能支持類似于數據庫的操作,以及函數式編程語言中的常用操作,如 filter、map、reduce、find、match、sort 等。流操作可以順序執行,也可并行執行。

此外,流操作有兩個重要的特點。

  • 流水線 —— 很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線。流水線的操作可以看作對數據源進行數據庫式查詢。

  • 內部迭代 —— 與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進行的。

流與集合

Java 8中的集合支持一個新的stream方法,它會返回一個流(接口定義在*java.util.stream.Stream里)。

那么,流到底是什么呢?簡短的定義就是“從支持數據處理操作的源生成的元素序列”。

  • 元素序列 —— 就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組有序值。因為集合是數據結構,所以它的主要目的是以特定的時間/空間復雜度存儲和訪問元素(如 ArrayList 與LinkedList)。但流的目的在于表達計算,比如前面見到的 filter、sorted 和 map。集合講的是數據,流講的是計算。

  • 源 —— 流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。

  • 數據處理操作 —— 流的數據處理功能支持類似于數據庫的操作,以及函數式編程語言中的常用操作,如 filter、map、reduce、find、match、sort 等。流操作可以順序執行,也可并行執行。
    此外,流操作有兩個重要的特點。

  • 流水線 —— 很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線。流水線的操作可以看作對數據源進行數據庫式查詢。

  • 內部迭代 —— 與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進行的。

流與集合

Java 現有的集合概念和新的流概念都提供了接口,來配合代表元素型有序值的數據接口。所謂有序,就是說我們一般是按順序取用值,而不是隨機取用的。

只能遍歷一次

和迭代器類似,流只能遍歷一次。遍歷完之后,我們就說這個流已經被消費掉了。你可以從原始數據源那里獲得一個新的流來重新遍歷一遍,就像迭代器一樣。例如,以下代碼會拋出一個異常,說流已經被消費掉了:

  List<String> title = Arrays.asList("Java8", "In", "Action");
  Stream<String> s = title.stream();
  s.forEach(System.out::println);    ←─打印標題中的每個單詞
  s.forEach(System.out::println);    ←─java.lang.IllegalStateException:流已被操作或關閉

外部迭代與內部迭代

使用 Collection 接口需要用戶去做迭代(比如用 for-each),這稱為外部迭代。相反,Streams 庫使用內部迭代——它幫你把迭代做了,還把得到的流值存在了某個地方,你只要給出一個函數說要干什么就可以了。下面的代碼列表說明了這種區別:

集合:用 for-each 循環外部迭代

  List<String> names = new ArrayList<>();
  for(Dish d: menu){                   ←─顯式順序迭代菜單列表
      names.add(d.getName());    ←─提取名稱并將其添加到累加器
  }

集合:用背后的迭代器做外部迭代

  List<String> names = new ArrayList<>();
  Iterator<String> iterator = menu.iterator();
  while(iterator.hasNext()) {                  ←─顯式迭代
      Dish d = iterator.next();
      names.add(d.getName());
  }

流:內部迭代

  List<String> names = menu.stream()
                           .map(Dish::getName)    ←─用getName 方法參數化map,提取菜名
                           .collect(toList());    ←─開始執行操作流水線;沒有迭代!

流操作

java.util.stream.Stream 中的 Stream 接口定義了許多操作。它們可以分為兩大類。

  • filter、map 和 limit 可以連成一條流水線;
  • collect 觸發流水線執行并關閉它。

可以連接起來的流操作稱為 中間操作,關閉流的操作稱為 終端操作。

中間操作

諸如 filter 或 sorted 等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理。在終端操作時一次性全部處理。

終端操作

終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如 List、Integer,甚至 void。

使用流

總而言之,流的使用一般包括三件事:

  • 一個數據源(如集合)來執行一個查詢;
  • 一個中間操作鏈,形成一條流的流水線;
  • 一個終端操作,執行流水線,并能生成結果。

流的流水線背后的理念類似于構建器模式。在構建器模式中有一個調用鏈用來設置一套配置(對流來說這就是一個中間操作鏈),接著是調用 built 方法(對流來說就是終端操作)。

表1:中間操作

操作 類型 返回類型 操作參數
filter 中間 Stream<T> Predicate<T>
map 中間 Stream<T> Function<T, R>
limit 中間 Stream<T>
sorted 中間 Stream<T> Comparator<T>
distinct 中間 Stream<T>

表2:終端操作

操作 類型 目的
forEach 終端 消費流中的每個元素并對其應用 Lambda。這一操作返回 void
count 終端 返回流中元素的個數。這一操作返回 long
collection 終端 把流歸約成一個集合,比如 ListMap 甚至是 Integer

小結

總結一下一些關鍵概念。

  • 流是“從支持數據處理操作的源生成的一系列元素”。
  • 流利用內部迭代:迭代通過 filter、map、sorted 等操作被抽象掉了。
  • 流操作有兩類:中間操作和終端操作。
  • filter 和 map 等中間操作會返回一個流,并可以鏈接在一起。可以用它們來設置一條流水線,但并不會生成任何結果。
  • forEach 和 count 等終端操作會返回一個非流的值,并處理流水線以返駕結果。
  • 流中的元素是按需計算的。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容