Java基礎 第五節 第七

      網友投稿 714 2022-05-30

      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 list = new ArrayList<>(); list.add("張無忌"); list.add("周芷若"); list.add("趙敏"); list.add("張強"); list.add("張三豐"); // 遍歷 for (String name : list) { System.out.println(name); } } } 輸出結果: 張無忌 周芷若 趙敏 張強 張三豐

      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 list = new ArrayList<>(); list.add("張無忌"); list.add("周芷若"); list.add("趙敏"); list.add("張強"); list.add("張三豐"); // 第一次過濾 List filtered1 = new ArrayList<>(); for (String name : list) { if (name.startsWith("張")) { filtered1.add(name); } } // 第二次過濾 List filtered2 = new ArrayList<>(); for (String name : filtered1) { if (name.length() == 3) { filtered2.add(name); } } // 遍歷 for (String name : filtered2) { System.out.println(name); } } } 輸出結果: 張無忌 張三豐

      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 list = new ArrayList<>(); list.add("張無忌"); list.add("周芷若"); list.add("趙敏"); list.add("張強"); list.add("張三豐"); list.stream() .filter(s -> s.startsWith("張")) .filter(s -> s.length() == 3) .forEach(System.out::println); } }

      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是 Java8 新加入的最常用的流接口. (這并不是一個函數式接口)

      獲取一個流非常簡單, 有以下幾種常用的方式:

      所有的 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 list = new ArrayList<>(); Stream stream1 = list.stream(); Set set = new HashSet<>(); // ... Stream stream2 = set.stream(); } }

      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 map = new HashMap<>(); // ... Stream keyStream = map.keySet().stream(); Stream valueStream = map.values().stream(); Stream> entryStream = map.entrySet().stream(); } }

      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 stream = Stream.of(array) } }

      1

      2

      3

      4

      5

      6

      7

      8

      注: of 方法的參數其實是一個可變參數, 所以支持數組.

      常用方法

      流模型的操作很豐富, 這里介紹一些常用的 API. 這些方法可以被分成兩種:

      延遲方法: 返回值類型仍然是 Stream 接口自身類型的方法, 因此支持鏈式調用 (除了終結方法外, 其余方法均為延遲方法)

      終結方法: 返回值類型不再是 Stream 接口自身類型的方法, 因此不再支持類似 StringBuilder 那樣的鏈式調用, 本課中, 終結方法包括 count 和 forEach 方法

      注: 本課之外的更多方法, 請自行參考 API 文檔.

      逐一處理: forEach

      雖然方法名字叫 forEach, 但是與 for 循環中的 “for-each” 昵稱不同.

      void forEach(Consumer action);

      1

      該方法接收一個 Consumer 接口函數, 會將每一個流元素交給該函數進行處理.

      復習 Consumer 接口

      java.util.function.Consumer 接口是一個消費型接口 Consumer 接口中包含抽象方法void accept(T t), 意為消費一個指定泛型的數據

      1

      2

      基本使用:

      import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream stream = Stream.of("張無忌", "張三豐", "周芷若"); stream.forEach(name -> System.out.println(name)); } }

      1

      2

      3

      4

      5

      6

      7

      8

      過濾: filter

      可以通過 filter 方法將一個流轉換成另一個子集流. 方法簽名:

      Stream filter(Predicate predicate);

      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 stream = Stream.of("張無忌", "張三豐", "周芷若"); Stream result = stream.filter(s -> s.startsWith("張")); } }

      1

      2

      3

      4

      5

      6

      7

      8

      在這里通過 Lambda 表達式來指定了篩選條件: 必須姓張.

      映射: map

      如果需要將流中的元素映射到另一流中, 可以使用 map 方法. 方法簽名:

      Stream map(Function mapper);

      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 original = Stream.of("10", "12", "18"); Stream result = original.map(str -> Integer.parseInt(str)); result.forEach(num -> System.out.println(num)); } } 輸出結果: 10 12 18

      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 original = Stream.of("張無忌", "張三豐", "周芷若"); Stream result = original.filter(s -> s.startsWith("張")); System.out.println(result.count()); // 輸出結果: 2 } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      取用前幾個: limit

      limit 方法可以對流進行截取, 只取用前 n 個. 方法簽名:

      Stream limit(long maxSize);

      1

      參數是一個 long 型, 如果集合當前長度大于參數進行截取, 否則不進行操作. 基本使用:

      import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream original = Stream.of("張無忌", "張三豐", "周芷若"); Stream result = original.limit(2); System.out.println(result.count()); // 輸出結果: 2 } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      跳過前幾個: skip

      如果希望跳過前幾個元素, 可以使用 skip 方法獲取一個截取之后的新流:

      Stream skip(long n);

      1

      如果流的當前長度大于 n, 則跳過前 n 個. 否則將會得到一個長度為 0 的空流. 基本使用:

      import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream original = Stream.of("張無忌", "張三豐", "周芷若"); Stream result = original.skip(2); System.out.println(result.count()); // 輸出結果: 1 } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      組合: concat

      如果有兩個流, 希望合并為一個流. 那么可以使用 Stream 接口的靜態方法 concat:

      Java基礎 第五節 第七課

      static Stream concat(Stream a, Stream b)

      1

      注: 這是一個靜態方法, 與java.lang.String當中的 conat 方法是不同的.

      該方法的基本使用代碼如:

      import java.util.stream.Stream; public class Test { public static void main(String[] args) { Stream streamA = Stream.of("張無忌"); Stream streamB = Stream.of("張翠山"); Stream result = Stream.concat(streamA, streamB); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      Java 數據結構

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:做成吉思汗的馬掌
      下一篇:部署一個端到端的IoT應用
      相關文章
      亚洲性猛交XXXX| 欧美激情综合亚洲一二区| 亚洲国产熟亚洲女视频| 亚洲成人福利网站| 亚洲视频在线免费播放| 亚洲伦另类中文字幕| 亚洲精品高清久久| 久久久久亚洲Av无码专| 亚洲国产一区二区a毛片| 亚洲国产香蕉碰碰人人| 亚洲日韩图片专区第1页| 亚洲人成在线影院| 亚洲最大在线观看| 亚洲乱码一二三四区国产| 亚洲13又紧又嫩又水多| 亚洲无吗在线视频| 亚洲欧洲日产国码久在线| 亚洲人av高清无码| 亚洲AV日韩AV永久无码色欲| 国产成人亚洲精品电影| 亚洲国产精品专区在线观看| 久久久久一级精品亚洲国产成人综合AV区| 亚洲人成色77777在线观看大| 国产成人精品日本亚洲专区| 国产亚洲精品激情都市| 亚洲精品国产精品乱码不卡√| 国产成人精品日本亚洲| 亚洲嫩草影院久久精品| 亚洲理论在线观看| 国产成人精品亚洲日本在线| 亚洲欧美熟妇综合久久久久| 国产精品亚洲精品日韩动图| 亚洲午夜av影院| 亚洲动漫精品无码av天堂| 久久亚洲私人国产精品vA | 亚洲啪啪免费视频| 精品亚洲国产成人| 亚洲AV无码XXX麻豆艾秋| 亚洲欧洲一区二区三区| 亚洲国产一二三精品无码| 久久亚洲精品无码AV红樱桃|