幾乎每一個 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)。