Java 8 | Stream (上)
Java 8 API
添加了一個新的抽象稱為流Stream
,可以讓你以一種聲明的方式處理資料。Stream
使用一種類似用SQL
語句從資料庫查詢資料的直觀方式來提供一種對Java
集合運算和表達的高階抽象。Stream API
可以極大提高Java
工程師的生產力,讓工程師寫出高效率、乾淨、簡潔的程式碼。
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。
元素流在管道中經過中間操作(intermediate operation
)的處理,最後由最終操作(terminal operation
)得到前面處理的結果。
什麼是 Stream?
Stream
(流)是一個來自資料源的元素隊列並支持聚合操作
- 元素是特定類型的對象,形成一個隊列。
Java
中的Stream
並不會存儲元素,而是按需計算。 - 資料源 流的來源。可以是集合,數組,
I/O channel
, 產生器generator
等。 - 聚合操作 類似SQL語句一樣的操作, 比如
filter
,map
,reduce
,find
,match
,sorted
等。
和以前的Collection
操作不同, Stream
操作還有兩個基礎的特徵:
Pipelining
:中間操作都會返回流對象本身。這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style
)。這樣做可以對操作進行優化, 比如延遲執行(laziness
)和短路(short-circuiting
)。- 內部迭代: 以前對集合遍歷都是通過
Iterator
或者For-Each
的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。Stream
提供了內部迭代的方式, 通過訪問者模式(Visitor
)實現。
生成流
在Java 8
中, 集合接口有兩個方法來生成流:
- stream() − 為集合創建串行流。
- parallelStream() − 為集合創建並行流。
1 | List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); |
forEach
Stream
提供了新的方法forEach
來迭代流中的每個資料。以下程式碼片段使用forEach
輸出了10
個隨機數:
1 | Random random = new Random(); |
輸出的結果為:(每次都不一樣)
1 | -66046892 |
map
map
方法用於映射每個元素到對應的結果,以下程式碼片段使用map
輸出了元素對應的平方數:
1 | List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); |
squaresList
為:
1 | [9, 4, 49, 25] |
filter
filter
方法用於通過設置的條件過濾出元素。以下程式碼片段使用 filter
方法過濾出空字符串:
1 | List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); |
count
為:
1 | 2 |
limit
limit
方法用於獲取指定數量的流。以下程式碼片段使用limit
方法印出10
條資料:
1 | Random random = new Random(); |
輸出的結果為:(每次都不一樣)
1 | 817080995 |
sorted
sorted
方法用於對流進行排序。以下程式碼片段使用sorted
方法對輸出的10
個隨機數進行排序:
1 | Random random = new Random(); |
輸出的結果為:(排序)
1 | -1374877061 |
統計
另外,一些產生統計結果的收集器也非常有用。它們主要用於int
、double
、long
等基本類型上,它們可以用來產生類似如下的統計結果。
1 | List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); |
輸出的結果為:
1 | 列表中最大的數 : 7 |
簡單流程說明
1 | +--------------------+ +------+ +------+ +---+ +-------+ |
以上的流程轉換為Java
程式為:
1 | List<Integer> transactionsIds = |
Stream之上的操作可分為中間操作和晚期操作
中間操作:會返回一個新的stream
——執行一個中間操作(例如filter
)並不會執行實際的過濾操作,而是創建一個新的stream
,並將原stream
中符合條件的元素放入新創建的stream
。
晚期操作:(例如forEach
或者sum
),會遍歷stream
並得出結果或者附帶結果;在執行晚期操作之後,stream
處理線已經處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對stream
進行遍歷。
範例
Stream API
極大得簡化了集合操作(不止是集合),首先看下這個叫Task
的類:
1 | public class Streams { |
Task
類有一個分數(或偽複雜度)的概念,另外還有兩種狀態:OPEN
或者CLOSED
。現在假設有一個task
集合:
1 | final Collection< Task > tasks = Arrays.asList( |
首先:在這個task
集合中一共有多少個OPEN
狀態的點?在Java 8
之前,要解決這個問題,則需要使用foreach
循環遍歷task
集合;但是在Java 8
中可以利用streams
解決:包括一系列元素的列表,並且支持順序和並行處理。
1 | // Calculate total points of all active tasks using sum() |
輸出的結果為:
1 | Total points: 18 |
tasks
集合被轉換成stream表示;stream
上的filter
操作會過濾掉所有CLOSED
的task
;mapToInt
操作基於每個task
實例的Task::getPoints
方法將task
流轉換成Integer
集合;- 通過
sum
方法計算總和,得出最後的結果。
總結
這邊大致上稍為介紹了Stream
的使用,透過範例並且實際去執行,在將來使用的時候才會有所印象;而這邊將Stream
分成上下篇,下篇將會針對parallel
和Collectors
特別說明。
註:以上參考了
Stream 與平行化
Java 8 Lambda新語法,簡化程式,增強效能
Java 8 新特性
Java 8的新特性—终极版
现代化 Java - Java8 指南