Java基礎 第五節 第七課
Stream 流
概述
傳統集合的多步遍歷代碼
循環遍歷的弊端
Stream 的更優寫法
流式思想概述
步驟方案
元素隊列
獲取流
根據 Collection 獲取流
根據 Map 獲取流
根據數組獲取流
常用方法
逐一處理: forEach
復習 Consumer 接口
過濾: filter
復習 Predicate 接口
映射: map
復習 Function 接口
統計個數: count
取用前幾個: limit
跳過前幾個: skip
組合: concat
概述
說到 Stream 便容易想到 I/O 流, 而實際上, 誰規定 “流” 就一定是 “IO流” 呢? 在 Java 8 中, 得益于 Lambda 所帶來的函數式編程, 引入了一個全新的 Stream 概念, 用于解決已有集合類庫既有的弊端.
傳統集合的多步遍歷代碼
幾乎所有的集合 (如 Collection 接口或 Map 接口等) 都支持直接或間接的變量操作. 而當我們需要對集合中元素進行操作的時候, 除了必需的添加, 刪除, 獲取外, 最典型的就是集合遍歷. 例如:
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
這是一段非常簡單的變量操作: 對集合中每一個字符串都進行打印輸出操作.
循環遍歷的弊端
Java 8 的 Lambda 讓我們可以更加專注于做什么 (What), 而不是怎么做 (How), 這點此前已經結合內部類進行了說明. 現在, 我們仔細體會一下上例代碼, 可以發現:
for 循環的語法就是 “怎么做”
for 循環的循環體才是 “做什么”
為什么要使用循環? 因為要進行遍歷. 但循環是遍歷的唯一方式嗎? 遍歷是指每一個元素逐一進行處理, 而并不是從第一個到最后一個順次處理的循環. 前者是目的, 后者是方式. 試想一下, 如果希望對集合中的元素進行篩選過濾:
將集合 A 根據條件一過濾為子集 B
然后再根據條件二過濾為子集 C
那怎么辦? 在 Java8 之前的做法可能為:
import java.util.ArrayList; import java.util.List; public class Test113 { public static void main(String[] args) { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
這段代碼中有三個循環, 每一個作用不同:
首先篩選所有姓張的人
然后篩選名字長度為三的人
最后對結果進行打印輸出
每當我們需要對結合中的元素進行操作的時候, 總是需要進行循環, 循環, 再循環. 這是理所當然的么? 不是. 循環是做事情的方式, 而不是目的. 另一方面, 使用線性循環就意味著只能遍歷一次. 如果希望再次變量, 只能在使用另一循環從頭開始.
那, Lambda 的衍生物 Stream 能給我們帶來怎樣更加優雅的寫法呢?
Stream 的更優寫法
下面來看一下借助 Java8 的 Stream API. 代碼如下:
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義: 獲取流, 過濾姓張, 過濾長度為 3, 逐一打印. 代碼中并沒有體現使用線性循環或者其他任何算法進行遍歷, 我們真正要做的事情內容被更好地體現在代碼中.
流式思想概述
我們先暫時忘記對傳統 IO 流的固有印象. 整體來看, 流式思想類似于工廠車間的 “生產流水線”.
步驟方案
當需要對多個元素進行操作 (特別是多步操作) 的時候, 考慮到性能及便利性, 我們應該首先拼好一個 “模型” 步驟方案, 然后再按照方案去執行它.
而方案就是一種 “函數模型”, 包括過濾, 映射, 跳過, 計數等多個步驟. 每一個步驟都是一個 “流”. 調用指定的方法, 可以從一個流模型轉換為另一個流模型, 并得到最終結果.
filter, map, skip 都是在對函數模型進行操作, 集合元素并沒有真正被處理. 只有當終結方法 count 執行的時候, 整個模型才會按照指定策略執行操作. 而這得益于 Lambda 的延遲執行特性.
注: “Stream 流” 其實是一個集合元素的函數模型. 它不是集合, 也不是數據結構, 其本身并不存儲任何元素 (或其地址值).
元素隊列
Stream (流) 是一個來自數據的元素隊列.
元素: 是特定類型的對象, 形成一個隊列. Java 中的 Stream 并不會存儲元素, 而是按需計算
數據源: 流的來源. 可以是集合, 數組等
和以前的 Collection 操作不同, Stream 操作還有兩個基礎特征:
Pipelining: 中間操作都會返回流對象本身. 這樣多個操作可以串聯成一個管道, 如同流式風格 (fluent style). 這樣做可以對操作進行優化, 比如延時 (laziness) 和短路 (short-circuiting)
內部迭代: 以前對集合遍歷都是通過 Iterator 或者增強 for 的方式, 顯示的在結合外部進行迭代, 這叫做外部迭代. Stream 提供了內部迭代的方式, 流可以直接調用遍歷方法.
當使用一個流的時候, 通常包括三個基本步驟: 獲取一個數據源 (source) -> 數據轉換 -> 執行操作獲取想要的結果. 每次轉換原有 Stream 對象不改變, 返回一個新的 Stream 對象 (可以有多次轉換), 這就允許其操作可以像鏈條一樣排序, 變成一個管道.
獲取流
java.util.stream.Stream
獲取一個流非常簡單, 有以下幾種常用的方式:
所有的 Collection 集合都可以通過 stream 默認方法獲取流
Stream 接口的靜態方法 of 可以獲取數組對應的流
根據 Collection 獲取流
首先, java.util.Collection接口中加入了 default 方法 stream 用來獲取流, 所以其所有實現類均可獲取流.
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Stream; public class Test { public static void main(String[] args) { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
根據 Map 獲取流
java.util.Map接口不是 Collection 的子接口, 且其 K-V 數據結構不符合流元素的單一特征. 所以獲取對應的流需要分 key, value 或 entry 等情況:
import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; public class Test { public static void main(String[] args) { Map
1
2
3
4
5
6
7
8
9
10
11
12
13
根據數組獲取流
如果使用的不是集合或映射而是數組, 由于數組對象不可能添加默認方法. 所以 Stream 接口中提供了靜態方法 of, 使用很簡單:
import java.util.stream.Stream; public class Test1 { public static void main(String[] args) { String[] array = {"張無忌", "張翠山", "張三豐", "張一元"}; Stream
1
2
3
4
5
6
7
8
注: of 方法的參數其實是一個可變參數, 所以支持數組.
常用方法
流模型的操作很豐富, 這里介紹一些常用的 API. 這些方法可以被分成兩種:
延遲方法: 返回值類型仍然是 Stream 接口自身類型的方法, 因此支持鏈式調用 (除了終結方法外, 其余方法均為延遲方法)
終結方法: 返回值類型不再是 Stream 接口自身類型的方法, 因此不再支持類似 StringBuilder 那樣的鏈式調用, 本課中, 終結方法包括 count 和 forEach 方法
注: 本課之外的更多方法, 請自行參考 API 文檔.
逐一處理: forEach
雖然方法名字叫 forEach, 但是與 for 循環中的 “for-each” 昵稱不同.
void forEach(Consumer super T> action);
1
該方法接收一個 Consumer 接口函數, 會將每一個流元素交給該函數進行處理.
復習 Consumer 接口
java.util.function.Consumer
1
2
基本使用:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
過濾: filter
可以通過 filter 方法將一個流轉換成另一個子集流. 方法簽名:
Stream
1
該接口接收一個 Predicate 函數接口參數 (可以是一個 Lambda 或方法引用) 作為篩選條件.
復習 Predicate 接口
此前我們已經學習過了javautil.stream.Predicate函數接口, 其中唯一的抽象方法為:
boolean test(T t);
1
該方法將會產生一個 boolean 值結果, 代表指定的條件是否滿足. 如果結果為 true, 那么 Stream 流的 filter 方法將會留用元素. 如果結果為 false, 那么 filter 方法將會舍棄元素.
Stream 流中的 filter 方法基本使用. 代碼如下:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
在這里通過 Lambda 表達式來指定了篩選條件: 必須姓張.
映射: map
如果需要將流中的元素映射到另一流中, 可以使用 map 方法. 方法簽名:
1
該接口需要一個 Function 函數接口參數, 可以將當前流中的 T 類型數據轉換為另一種 R 類型的流.
復習 Function 接口
此前我們已經學習過java.util.stream.Function函數式接口, 其中唯一的抽象方法為:
R apply(T t);
1
這可以將一種 T 類型轉換成為 R 類型. 而這種轉換的動作, 就稱為 “映射”.
Stream 流中的 map 方法基本使用的代碼如:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
這段代碼中, map 方法的參數通過方法引用, 將字符串類型轉換成為了 int 類型. (并自動裝箱為 Integar 類對象)
統計個數: count
正如舊集合 Collection 當中的 size 方法一樣. 流提供 count 方法來數一數其中的元素個數:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
9
取用前幾個: limit
limit 方法可以對流進行截取, 只取用前 n 個. 方法簽名:
Stream
1
參數是一個 long 型, 如果集合當前長度大于參數進行截取, 否則不進行操作. 基本使用:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
9
跳過前幾個: skip
如果希望跳過前幾個元素, 可以使用 skip 方法獲取一個截取之后的新流:
Stream
1
如果流的當前長度大于 n, 則跳過前 n 個. 否則將會得到一個長度為 0 的空流. 基本使用:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
9
組合: concat
如果有兩個流, 希望合并為一個流. 那么可以使用 Stream 接口的靜態方法 concat:
static
1
注: 這是一個靜態方法, 與java.lang.String當中的 conat 方法是不同的.
該方法的基本使用代碼如:
import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream
1
2
3
4
5
6
7
8
9
Java 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。