一、前言
Java團隊規劃函數化Lambda操作,讓Java物件導向的語言更簡潔,操作起來很有函數的風格。可是在引入Lambda的概念前,Java可是純物件導向,那些根深蒂固的物件要如何改造才能相容以前的寫法,又可以重生使用函數化操作呢?
規劃流水線,使用建造者模式(Builder Design Pattern)的概念,一個函數,一個功能,慢慢完成最終的成品。
新建立函數化風格的套件- java.util.stream ,在這個套件內的所有Class都是支持函數化操作。
Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections.
目錄
二、Stream串流
該規劃最關鍵的抽象概念是
stream
(流)。可以通過Stream、IntStream、LongStream、DoubleStream讓物件(Objects)與基本型別(primitive type)進入
stream
的操作中。
- No storage:流不是儲存元素的資料結構;而是儲存元素的資料結構。相反,它透過計算操作的管道傳送來自資料結構、陣列、生成器函數或 I/O 通道等來源的元素。
- Functional in nature:對流的操作會產生結果,但不會修改其來源。例如,過濾從集合中取得的 a 會產生一個沒有過濾元素的新元素,而不是從來源集合中刪除元素。
- Laziness-seeking:許多串流操作(例如過濾、映射或重複刪除)可以延遲實現,從而提供最佳化的機會。例如,「尋找第一個具有三個連續母音的」不需要檢查所有輸入字串。流操作分為中間(產生)操作和終端(產生值value-producing或side-effect-producing)操作。中間運算總是Lazy。
- Possibly unbounded:雖然集合的大小是有限的,但流則不需要。諸如 limit(n) 或 findFirst() 之類的Short-circuiting operations可以允許在有限時間內在無限流上計算完成。
- Consumable:在流的生命週期中,流的元素只被存取一次。與迭代器一樣,必須產生新流才能重新訪問來源中的相同元素。
可以從以下這些方式獲取流Streams:
-
Collection
透過stream()
和parallelStream()
方法獲取。 -
陣列透過
Arrays.stream(Object[]);
方法獲得。 -
檔案的行數透過
BufferedReader.lines();
方法獲得。 -
檔案流透過
Files
的方法find、list、walk
等方法獲得。 -
亂數流可透過
Random.ints();
方法獲得。 -
在java.util.stream套件包下物件的靜態方法也可以獲得,如
Stream.of(Object[])
,IntStream.range(int, int)
orStream.iterate(Object, UnaryOperator)
;等靜態方法獲得。
流的操作分為
intermediate
轉換管道(如:filter())和
terminal
終止管道(如:forEach()),組合進
pipelines
管線流中。轉換管道不會立即取得結果,而是會執行處理後放入一個新創建的Stream再返回給您;終止管道則是一個Consumer,管線流到這邊就結束不再能使用。
graph LR A[Input] -->|Stream| B(operation 1) subgraph **Intermediate Operations** B --> C(operation 2) C -.-> D(operation n) end D --> E(Terminal Operation) E --> F(Output)
Intermediate Operations
API | 功能說明 |
---|---|
filter() | 依照條件過濾符合要求的元素,傳回新的streamflowmap()將已有元素轉換為另一個物件類型,重複一邏輯,傳回新的stream |
flatMap() | 將已有元素轉換為另一個對象類型,原來多邏輯,即一個元素物件可以轉換為1個或多個新類型的元素,傳回新的stream |
limit() | 只保留集合前面指定的個數的元素,新的stream流 |
skip() | 跳過集合前面指定的個數的元素,傳回新的stream流 |
concat() | 將兩個流的資料合併為1個新的流,傳回新的stream流 |
distinct() | 對Stream中所有元素進行重整,傳回新的stream流 |
sorted() | 將stream中所有元素依照指定規則排序,傳回新的stream流 |
peek() | 對stream流中的每個元素進行逐一遍歷處理,返回處理後的stream |
Terminal Operations
API | 功能說明 |
---|---|
count() | 返回stream處理後最終的元素個數 |
max() | 返回stream處理後的元素最大值 |
min() | 返回stream處理後的元素最小值 |
findFirst() | 找到第一個符合條件的元素時則終止流處理 |
findAny() | 找到任何一個符合條件的元素時則退出流處理,此對於串行流時與findFirst相同,對於單個流時高效比較,任何分片中找到終止後續計算邏輯 |
anyMatch() | 返回一個boolean值,相似isContains(),用來判斷是否符合條件的元素 |
allMatch() | 傳回一個boolean值,用於判斷所有元素是否都符合條件 |
noneMatch() | 傳回一個boolean值,用於判斷是否所有元素不符合條件 |
collect() | 將流轉換為指定的類型,透過Collectors進行指定toArray()將流轉換為Arrayiterator() |
三、串流的操作
- Intermediate: map (mapToInt, flatMap, etc.), filter, distinct, sorted, peek, limit, skip, parallel, sequential, unordered
- Terminal: forEach, forEachOrdered, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAny, iterator
- Short-circuiting: anyMatch, allMatch, noneMatch, findFirst, findAny, limit Let’s take a look at the more typical uses of Stream.
我們來看看Stream的比較典型的用法
(3.1) forEach
對此流的每個元素執行一個操作。
範例:輸出集合中所有元素
輸出結果:
(3.2) map
Stream map(Function mapper)傳回一個流,其中包含將給定函數應用於該流的元素的結果。
Stream map(Function Mapper)是一個 intermediate operation。這些操作總是lazy。中間操作在 Stream 實例上調用,在完成處理後,它們提供 Stream 實例作為輸出。
範例:將集合中的數字乘以3
輸出結果:
(3.3) flatMap
Stream flatMap(Function mapper)傳回一個流,其中包含將此流的每個元素替換為通過將提供的映射函數應用於每個元素而生成的映射流的內容的結果。是一個 intermediate operation。
flatMap() v.s. map() 的調用:
- map() 將流的每個元素轉換為另一個對象,從而生成與輸入大小相同的流。它用於一對一轉換。
- flatMap() 將流的每個元素轉換為零個或多個元素,從而可能會改變流的大小。它用於一對多轉換和扁平化嵌套結構。
範例:將集合扁平化輸出
輸出結果:
(3.4) filter
Stream filter(Predicate predicate)傳回一個流,該流由此流中與給定Predicate匹配的元素組成。這是一個intermediate operation。 這些操作始終是lazy,即執行諸如 filter() 之類的中間操作實際上並不執行任何過濾,而是創建一個新流,該流在遍歷時包含與給定predicate匹配的初始流的元素。
範例:從集合中篩選出被5整除的數字
輸出結果:
(3.5) findFirst
Stream findFirst() 傳回描述此流的第一個元素的 Optional(容器物件,可能包含也可能不包含非 null 值),如果流為空,則返回空的 Optional。如果流沒有遭遇順序,則可以返回任何元素。
範例:回傳集合中第一個元素
輸出結果:
(3.6) reduce
Stream.reduce() 方法用於使用關聯累加函數對流的元素執行縮減,並返回一個Optional。它通常用於將元素聚合或組合成單個結果,例如計算最大值、最小值、總和或乘積。
範例1:串接字串
輸出結果:
範例2:將數字相乘
輸出結果:
(3.7) limit
Stream.limit(long maxSize) 的方法將maxSize作為參數,並返回大小不超過 maxSize 的流。如果 maxSize 的值很大,則 limit() 在有序並行管道上可能會效能消費很高,因為 limit(long maxSize) 被限制返回按順序返回前 maxSize 個元素。
範例:限制集合長度為3
輸出結果:
(3.8) skip
在丟棄流的前 n 個元素後,返回由該流的剩餘元素組成的流。
範例:輸出偶數,但跳過前2個
輸出結果:
(3.9) sorted
Stream sorted() 傳回一個由此 stream 的元素組成的 stream,根據自然 Sequences 排序。對於有序流,排序方法是穩定的,但對於無序流,不能保證穩定性。它是一個有狀態的中間操作,即,在處理新元素時,它可能會合併以前看到的元素的狀態。
範例:對集合中數字作自然排序
輸出結果:
(3.10) max/min
Stream.max() 根據提供的 Comparator 傳回 stream 的最大元素。Comparator 是一個比較函數,它對某些物件集合施加總排序。max() 是一個終端操作,它組合了 stream 元素並返回一個 summary 結果。所以, max() 是 reduction 的一個特例。該方法返回 Optional 實例。
Stream.min() 則為傳回 stream 的最小元素。
範例:取得集合內最大與最小值
輸出結果:
(3.11) distinct
distinct() 傳回一個由 Stream 中的不同元素組成的 Stream。distinct() 是 Stream 介面的方法。此方法使用 hashCode() 和 equals() 方法來獲取不同的元素。在有序流的情況下,不同元素的選擇是穩定的。但是,在無序流的情況下,不同元素的選擇不一定穩定,並且可能會發生變化。distinct() 執行stateful intermediate operation,即它在內部維護一些狀態以完成操作。
範例:取得集合內不重複的元素
輸出結果:
(3.12) match
- Stream allMatch(Predicate predicate) Stream 中全部元素符合指定的predicate,傳回 true。
- Stream anyMatch(Predicate predicate) Stream只要有一個符合指定predicate的元素,回傳 true。
- Stream noneMatch(Predicate predicate) Stream中沒有一個元素符合指定的predicate,回傳true。
如果不是確定結果所必需的,它可能不會對所有元素的predicate求值。這3個都是short-circuiting terminal operation。如果終端操作在具有無限輸入時可能在有限時間內終止,則它是short-circuiting。
範例:在集合中判斷是否匹配所有符合、任一符合或都不符合計算條件
輸出結果:
(3.13) generate
Stream generate(Supplier
範例:隨機生成5個整數
輸出結果:
(3.14) iterate
iterate(T seed, Predicate<? super T> hasNext, UnaryOperator
如果傳遞的 predicate 不持有 seed 值,則此方法返回的結果序列可能為空。否則,第一個元素將是提供的種子值,下一個元素將是將 next 函數應用於種子值的結果,依此類推,直到 hasNext predicate 指示流應終止。
範例:
輸出範例:
(3.14) collect
它允許我們對 Stream 實例中保存的數據元素執行mutable Fold(higher-order function) operations(將元素重新打包到一些數據結構並應用一些額外的邏輯、連接它們等)。
範例:將產生出的數字收集成List
輸出結果:
四、總結
我們大致上瞭解 Interface Stream 的操作方式,在 套件java.util.stream 中還有其他Stream,如:DoubleStream、IntStream、LongStream......等等,其操作方式和概念都是雷同的。
仍然還有許多概念並沒有提到很多,如
- Parallelism
- Side-effects
- Reduction operations
- Mutable reduction
- concurrency
- Associativity
引入Stream將改變集合生態,更是新增
Interface Collector<T,A,R>
等介面強化集合生態,優化、提升效能與簡化操作將是一大亮點。
沒有留言:
張貼留言