1. 程式人生 > >《Java 8 in Action》Chapter 4:引入流

《Java 8 in Action》Chapter 4:引入流

1. 流簡介

流是Java API的新成員,它允許你以宣告性方式處理資料集合(通過查詢語句來表達,而不是臨時編寫一個實現)。就現在來說,你可以把它們看成遍歷資料集的高階迭代器。此外,流還可以透明地並行處理。讓我們來看一個例項返回低熱量(<400)的菜餚名稱:

Java7版本:
List<Dish> lowCaloricDishes = new ArrayList<>();
// 用累加器篩選元素
for(Dish d: menu){
    if(d.getCalories() < 400){
        lowCaloricDishes.add(d);
    }
}
// 用匿名類對菜餚排序
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
    public int compare(Dish d1, Dish d2){
        return Integer.compare(d1.getCalories(), d2.getCalories());
    }
});
// 處理排序後的菜名列表
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d: lowCaloricDishes){
    lowCaloricDishesName.add(d.getName());
}
Java8版本:
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName = menu.stream()
                                        .filter(d -> d.getCalories() < 400)    // 選出400卡路里以下的菜餚
                                        .sorted(comparing(Dish::getCalories))    // 按照卡路里排序
                                        .map(Dish::getName)                    // 提取菜餚名稱
                                        .collect(toList());                    // 將所有的名稱儲存在List中
利用多核架構並行執行,只需要把stream()換成parallelStream()

Java 8中的Stream API特性:

  • 宣告性——更簡潔,更易讀
  • 可複合——更靈活
  • 可並行——效能更好

流定義:

  • 元素序列——就像集合一樣,流也提供了一個介面,可以訪問特定元素型別的一組有序 值。
  • 源——流會使用一個提供資料的源,如集合、陣列或輸入/輸出資源。 請注意,從有序集 合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
  • 資料處理操作——流的資料處理功能支援類似於資料庫的操作,以及函數語言程式設計語言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以順序執行,也可並行執行。
  • 流水線——很多流操作本身會返回一個流,這樣多個操作就可以連結起來,形成一個大的流水線。這讓我們下一章中的一些優化成為可能,如延遲和短路。流水線的操作可以看作對資料來源進行資料庫式查詢。
  • 內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。

2. 流與集合

集合與流之間的差異就在於什麼時候進行計算。集合是一個記憶體中的資料結構,它包含資料結構中目前所有的值——集合中的每個元素都得先算出來才能新增到集合中。相比之下,流則是在概念上固定的資料結構(你不能新增或刪除元素),其元素則是按需計算的。集合和流的另一個關鍵區別在於它們遍歷資料的方式。

2.1 只能遍歷一次

和迭代器類似,流只能遍歷一次。遍歷完之後,我們就說這個流已經被消費掉了。以下程式碼會丟擲一個異常,說流已被消費掉了:

List<String> title = Arrays.asList(“Java8”,”In”, “Action”);
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.lujiahao.learnjava8.chapter4.StreamAndCollection.main(StreamAndCollection.java:16)

2.2 外部迭代與內部迭代

使用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)
                        .collect(toList());

3. 流操作

java.util.stream.Stream中的Stream介面定義了許多操作。它們可以分為兩大類。可以連線起來的流操作稱為中間操作,關閉流的操作稱為終端操作。
中間操作:除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理。
終端操作:會從流的流水線生成結果。其結果是任何不是流的值。

流的使用一般包括三件事:

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

流的流水線背後的理念類似於構建器模式。

常見流操作:

4. 小結

以下是你應從本章中學到的一些關鍵概念。

  • 流是“從支援資料處理操作的源生成的一系列元素”。
  • 流利用內部迭代:迭代通過filter、map、sorted等操作被抽象掉了。
  • 流操作有兩類:中間操作和終端操作。
  • filter和map等中間操作會返回一個流,並可以連結在一起。可以用它們來設定一條流水線,但並不會生成任何結果
  • forEach和count等終端操作會返回一個非流的值,並處理流水線以返回結果。
  • 流中的元素是按需計算的。

資源獲取

  • 公眾號回覆 : Java8 即可獲取《Java 8 in Action》中英文版!

Tips

  • 歡迎收藏和轉發,感謝你的支援!(๑•̀ㅂ•́)و✧
  • 歡迎關注我的公眾號:莊裡程式猿,讀書筆記教程資源第一時間獲得!