引入流

流是什么

它允許你以聲明性方式處理數(shù)據(jù)集合(通過查詢語句來表達,而不是臨時編寫一個實現(xiàn))??梢园阉鼈兛闯杀闅v數(shù)據(jù)集的高級迭代器。此外,流還可以透明地并行處理。下面的討論,將會使用到這樣一個例子:一個 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 里)。

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

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

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

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

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

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

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

流與集合

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

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

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

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

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

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

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

流與集合

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

只能遍歷一次

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

  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:流已被操作或關(guān)閉

外部迭代與內(nèi)部迭代

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

集合:用 for-each 循環(huán)外部迭代

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

流:內(nèi)部迭代

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

流操作

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

  • filter、map 和 limit 可以連成一條流水線;
  • collect 觸發(fā)流水線執(zhí)行并關(guān)閉它。

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

中間操作

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

終端操作

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

使用流

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

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

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

表1:中間操作

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

表2:終端操作

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

小結(jié)

總結(jié)一下一些關(guān)鍵概念。

  • 流是“從支持數(shù)據(jù)處理操作的源生成的一系列元素”。
  • 流利用內(nèi)部迭代:迭代通過 filter、map、sorted 等操作被抽象掉了。
  • 流操作有兩類:中間操作和終端操作。
  • filter 和 map 等中間操作會返回一個流,并可以鏈接在一起。可以用它們來設(shè)置一條流水線,但并不會生成任何結(jié)果。
  • forEach 和 count 等終端操作會返回一個非流的值,并處理流水線以返駕結(jié)果。
  • 流中的元素是按需計算的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容