引入流

流是什麼

幾乎每一個 Java 應用都會製造和處理集合。 容許咱們以聲明性方式處理集合(經過相似於 SQL 查詢語句來表達,而不是臨時寫一個實現)。此外 還能夠透明地並行處理,無需寫任何多線程代碼。java

先看一個例子,混混眼熟就行數據庫

List<String> result = dishList
                .parallelStream()
                // 過濾
                .filter(d -> d.getCalories() < 400)
                // 排序
                .sorted(Comparator.comparing(Dish::getCalories))
                // 映射
                .map(Dish::getName)
                // 保存
                .collect(Collectors.toList());

並且以 parallelStream 調用會利用多核架構並行執行這段代碼,若是想要單線程處理,只須要把 parallelStream 換成 stream。數組

從代碼能夠看出,它是以聲明性方式寫的:filter、sorted、map 和 collect。如同 SQL 中的 SELECT、FROM、WHERE 同樣。數據結構

Java 8中的 Stream API 能夠多線程

  • 聲明性:更簡潔架構

  • 可複合:更靈活函數

  • 可並行:性能更好性能

流簡介

流的定義是 從支持數據處理操做的源生成的元素序列this

  • 元素序列:流提供了一個接口,能夠訪問特定元素類型的一組有序值線程

  • 源:流會使用一個提供數據的源,如集合、數組或輸出/輸出

  • 數據處理操做:流的數據處理功能支持相似數據庫的聲明式操做,能夠串行處理也可並行

  • 流水線:不少流操做自己會返回一個流,這樣多個操做就能夠連接起來,造成一個大的流水線

  • 內部迭代:與使用迭代器顯式迭代不一樣,流的迭代操做是在背後執行的

爲了更直觀地理解流以及 Lambda 在其的應用,咱們能夠先建立一個菜餚類

public class Dish {
    // 名字
    private String name;
    // 是否素食
    private Boolean vegetarian;
    // 卡路里
    private Integer calories;
    // 類型
    private Type type;

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

    // 類型:肉、魚、其餘
    public enum Type {
        MEAT, FISH, OTHER
    }

    // Getter and Setter
}

而後用構造函數定義一個菜單 List,好讓咱們對其進行流的演示

List<Dish> menu = new ArrayList<>();
        // 豬肉:非素食,800卡路里,肉類
        menu.add(new Dish("pork", false, 800, Dish.Type.MEAT));
        // 牛肉:非素食,700卡路里,肉類
        menu.add(new Dish("beef", false, 700, Dish.Type.MEAT));
        // 米飯:素食,359卡路里,其餘
        menu.add(new Dish("rice", true, 350, Dish.Type.OTHER));
        // 披薩:素食,550卡路里,其餘
        menu.add(new Dish("pizza", true, 550, Dish.Type.OTHER));
        // 對蝦:非素食,350卡路里,魚類
        menu.add(new Dish("prawns", false, 350, Dish.Type.FISH));
        // 三文魚:非素食,450卡路里,魚類
        menu.add(new Dish("salmon", false, 450, Dish.Type.FISH));

接下來咱們使用流找出頭三個高熱量菜餚的名字

List<String> threeHighCaloriesDishNames = menu
                // 從 menu 得到源
                .stream()
                // Lambda 調用謂詞函數式接口過濾卡路里大於400的高熱量菜餚
                .filter((dish) -> dish.getCalories() > 400)
                // 將過濾結果映射成對應的菜餚名
                .map(Dish::getName)
                // 按照順序選擇三個
                .limit(3)
                // 保存成 List
                .collect(Collectors.toList());

其中兩個 Lambda 對應的是如下的快捷寫法

// 返回一個判斷卡路里是否高於400的謂詞接口對象
        Predicate<Dish> dishPredicate = (Dish dish) -> dish.getCalories() > 400;
        // 返回一個映射菜餚名稱的映射接口對象
        Function<Dish, String> dishStringFunction = (Dish dish) -> dish.getName();

若是看不懂請回顧上兩章內容。涉及到行爲參數化和 Lambda 的多種可簡寫方式。

在這個小小的例子裏,有不少概念。咱們先是對 menu 調用 stream 方法獲得一個流,因此能夠稱 menu 爲 。它給流提供一個 元素序列,接下來對流進行的一系列 數據處理操做 都會返回另外一個流:filter、 map、limit,這樣它們就能夠拼接成一條 流水線。最後 collect 操做開始處理流水線,並返回結果。在調用 collect 以前沒有任何結果產生,實際上根本就沒有從 menu 裏選擇元素。能夠這麼說,在流水線裏的方法調用都在排隊等待,直到調用 collect。

流與集合

粗略地說,流與集合之間的差別就在於何時進行計算。流是在概念上固定的數據結構,其元素則是按需計算的。例如,儘管質數有無窮多個,但咱們僅僅須要從流中提取須要的值,而這些值只會按需生成。與此相反,集合想要建立一個包含全部質數的集合,那會計算起來沒完沒了,由於總有新的質數要計算,這樣咱們就不能從中取得須要的值。

和迭代器同樣,流只能遍歷一次,遍歷完後咱們就能夠說這個流被消費掉了。咱們能夠從源再得到一個新的流來從新遍歷。

用同一個流遍歷 menu 兩次會拋出異常

Stream<Dish> dishStream = menu.stream();
        // 等同於 dishStream.forEach((Dish dish) -> System.out.println(dish));
        dishStream.forEach(System.out::println);
        // java.lang.IllegalStateException: stream has already been operated upon or closed
        // 流已經被操做或關閉
        dishStream.forEach(System.out::println);

外部迭代與內部迭代

集合須要咱們手動去作迭代,好比 for-each,這樣被稱爲外部迭代。相反,流使用內部迭代。

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

List<String> dishNames = new ArrayList<>();
        for (Dish dish : menu) {
            dishNames.add(dish.getName());
        }

流:內部迭代

List<String> dishNames = menu
                .stream()
                .map(Dish::getName)
                .collect(Collectors.toList());

乍一看好像沒有多大的區別,可是若是考慮到並行處理呢?若是使用外部迭代則須要咱們很痛苦地本身去處理並行,而用流則很是簡單,只須要把 stream 換成 parallelStream。

流的使用通常包括三件事:

  • 一個數據源來執行一個查詢

  • 一箇中間操做鏈,造成一條流水線

  • 一個終端操做,執行流水線並生成結果

以上即是流的一些基礎知識,下一章會更加深刻理解流。

Java 8 實戰 第四章 引入流 讀書筆記

歡迎加入咖啡館的春天(338147322)。

相關文章
相關標籤/搜索