1. 程式人生 > >Java流(Stream)簡介

Java流(Stream)簡介

流簡介

要討論流,我們先來談談集合,這是最容易上手的方式了。 Java 8中的集合支援一個新的stream方法,它會返回一個流(介面定義在java.util.stream.Stream裡)。那麼, 流到底是什麼呢?簡短的定義就是“從支援資料處理操作的源生成的元素序列”。讓我們一步步剖析這個定義。

  1. 元素序列:就像集合一樣,流也提供了一個介面,可以訪問特定元素型別的一組有序值。因為集合是資料結構,所以它的主要目的是以特定的時間/空間複雜度儲存和訪問元素(如ArrayList 與 LinkedList)。但流的目的在於表達計算,比如filter、 sorted和map。集合講的是資料,流講的是計算。
  2. 源:流會使用一個提供資料的源,如集合、陣列或輸入/輸出資源。 請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
  3. 資料處理操作:流的資料處理功能支援類似於資料庫的操作,以及函數語言程式設計語言中的常用操作,如filter、 map、 reduce、 find、 match、 sort等。流操作可以順序執行,也可並行執行。
  4. 流水線:很多流操作本身會返回一個流,這樣多個操作就可以連結起來,形成一個大的流水線,流水線的操作可以看作對資料來源進行資料庫式查詢。
  5. 內部迭代:與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。

流與集合

Java現有的集合概念和新的流概念都提供了介面,來配合代表元素型有序值的資料介面。所謂有序,就是說我們一般是按順序取用值,而不是隨機取用的。

粗略地說,集合與流之間的差異就在於什麼時候進行計算。集合是一個記憶體中的資料結構,它包含資料結構中目前所有的值,集合中的每個元素都得先算出來才能新增到集合中。(你可以往集合里加東西或者刪東西,但是不管什麼時候,集合中的每個元素都是放在記憶體裡的,元素都得先算出來才能成為集合的一部分。)

相比之下,流則是在概念上固定的資料結構(你不能新增或刪除元素),其元素則是按需計算的。 這對程式設計有很大的好處。這個思想就是使用者僅僅從流中提取需要的值,而這些值在使用者看不見的地方,只會按需生成。這是一種生產者,消費者的關係。從另一個角度來說,流就像是一個延遲建立的集合,只有在消費者要求的時候才會計算值(用管理學的話說這就是需求驅動,甚至是實時製造)。

請注意,和迭代器類似,流只能遍歷一次。遍歷完之後,我們就說這個流已經被消費掉了。你可以從原始資料來源那裡再獲得一個新的流來重新遍歷一遍,就像迭代器一樣(這裡假設它是集合之類的可重複的源,如果是I/O通道就沒戲了)。

外部迭代與內部迭代

使用Collection介面需要使用者去做迭代(比如用for-each),這稱為外部迭代。 相反,Streams庫使用內部迭代,它幫你把迭代做了,還把得到的流值存在了某個地方,你只要給出一個函式說要幹什麼就可以了。

流操作

java.util.stream.Stream中的Stream介面定義了許多操作,它們可以分為兩大類:中間操作和終端操作,可以連線起來的流操作稱為中間操作,關閉流的操作稱為終端操作。

中間操作和終端操作

諸如filter或sorted等中間操作會返回另一個流。這讓多個操作可以連線起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理,它們很懶。這是因為中間操作一般都可以合併起來,在終端操作時一次性全部處理。

終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如List、 Integer,甚至void。

總而言之,流的使用一般包括三件事:

  1. 一個數據源(如集合)來執行一個查詢;
  2. 一箇中間操作鏈,形成一條流的流水線;
  3. 一個終端操作,執行流水線,並能生成結果;

常用中間操作

操作 型別 返回型別 操作引數 函式描述符
filter 中間 Stream<T> Predicate<T> T -> boolean
map 中間 Stream<R> Function<T, R> T -> R
limit 中間 Stream<T>
sorted 中間 Stream<T> Comparator<T> (T, T) -> int
distinct 中間 Stream<T>

常用終端操作

操作 型別 目的
forEach 終端 消費流中的每個元素並對其應用 Lambda。這一操作返回 void
count 終端 返回流中元素的個數。這一操作返回 long
collect 終端 把流歸約成一個集合,比如 List、 Map 甚至是 Integer。

參考:Java8實戰