Java 8 初探 - Stream

更新於 發佈於 閱讀時間約 7 分鐘
此為過去的舊文,2014 年 4 月 13 日初次發表於 logdown。

即使沒有 Stream,Java Collection framework 的設計仍是相當不錯,只是有時候需要一些簡單的功能,例如:根據某些條件查找容器中的某個物件,總是要寫個 for 迴圈,程式不難但寫久了也覺得煩。在沒有類似 for in 的語法糖衣之前,index 的管理很惱人。若是巢狀迴圈,index 命名不好遇到問題 debug 起來更是頭痛,用過 Apache Commons Collections 後,Apache Commons Collections 幾乎是專案裡必備的套件。先來個簡單例子,假設想在放 Person 物件的 List 中找姓名含某個特定值時,傳統的寫法如下,會是寫一個 for 迴圈,然後逐一檢查每個person物件的姓和名。

那如果用 Apache Commons Collections 又會如何呢?請看下面的範例,基本上不需要寫 for 迴圈,只要在呼叫 find 時傳一個實作 Predicate 介面的物件即可,該物件只需要實作 evaluate 這個method,判斷是否滿足條件,滿足回傳 true,就這樣。什麼!程式碼行數變多,沒錯,確實變多,但 PersonNamePredicate 這物件是可以重複使用的,若觀察 CollectionUtils 這個 class 就會發現有 12 個 methods 利用 Predicate 物件對容器進行過濾、選擇、計數等操作,加上 Predicate 的實作要測試很容易,所以這樣寫很划算。

好啦!既然主題是 Java 8 新的 Stream API,那用 Stream 該怎麼寫?Stream 的寫法會像下面範例,好像沒有比較省行數,但和原始的 for 迴圈相比,就語意上或是可讀性上,這版本可以解讀成『根據一某條件過濾,然後找第一個,如果結果存在就回傳該物件,不存在就回傳 null』,迴圈的操過被忽略了,是否有感覺抽象程度被提高了呢?

那如果要像 Apache Commons Collections 那樣,可以辦到嗎?可以!首先,像下例,寫個 StreamUtils 輔助類別,提供一個 find(Collection, Predicate) 函式,然後改寫 PersonNamePredicate,接著就可以只寫一行就搞定。當然,如果不打算讓 PersonNamePredicate 同時支援 Apache Commons Collections 及 Java Stream,只需實作 java.util.Predicate 介面的 test 函式就好。

不過繞了一大圈,為的是什麼?除了使用parallelStream()可能帶來平行處理的好處外,這個版本並沒有帶來太多的好處,主要是應用(find)太簡單了,用Stream有點殺雞用牛刀的感覺。

Java Stream API 的概念類似 Unix Pipelinepipes and filters design pattern,透過串接多個簡單的 operation 完成有意義的工作,由於 operation 通常都很簡單,所以使用 Lambda expression 多數時候可以帶來簡潔和提升可讀性的好處。


Figure 1 - Stream Pipeline

Figure 1 - Stream Pipeline


如 Figure 1 所示,Java Stream 能串多個 intermediate operations,但最後只能串一個 terminal operation 來組成 pipeline。用 intermediate operation 轉換 stream 內容,例如:過濾 (filter(Predicate))、替換 (map(Function))、排序 (sorted(Comparator)) 等,然後用 terminal operation 對 stream 內的資料計算最終結果或產生 side effect,例如:收集 (collect(Collector))、逐一改變 (forEach(Consumer)) 或歸納 (Reduce(BinaryOperator)) 等。


Figure 2 - Stream Pipeline Example

Figure 2 - Stream Pipeline Example


例如,可以用 Figure 2 的 pipeline 來計算資料中資產超過 10 億元的富豪,其資產的總合,首先 filter(Predicate) 過濾出資產超過 10 億元的資料,接著用 map(Function) 取出資產的部分,最後用 reduce(BinaryOperation) 做歸納。事實上,類似的運算實在太常用了,因此 Java Stream API 中有個 Collectors 類別提供常用的 terminal operation,例如 summarizingDouble(ToDoubleFunction) 就結合了 map(Function) 和預設的 reduce(BinaryOperation) 實作,簡化 pipeline 的組成。

覺得例子有點抽象?那再來一個更具體的例子吧。假設 Exam 代表一種測驗,每個人可以參加多次測驗,因此 Person 有一個 List 存放受測者參加過的所有測驗。假如想要取得曾經得超過 700 分的所有受測者排名,為了不寫重複的程式碼,如下面的範例程式,先將取得受測者曾經參加過的測驗最高分寫成 PersongetHighestScore() 函式 (仍用Stream API)。

接著就可以寫一個 showRank(List<Person>, double) 的函式,第一個參數是所有受測者,第二個參數是排行榜顯示的門檻。程式首先呼叫 stream() 取得 Stream 物件,接著對 Stream 呼叫 filter(Predicate) 函式,這裡用 Lambda Expression 就覺得很自然,也提高可讀性。

過濾掉低於門檻值的受測者後,呼叫 sorted(Comparator) 進行排序,然後 map(Function) 將受測者的全名與分數組成字串 (例如:"Spirit Tu: 840.0") 當成結果,這邊要小心的是 map(Function) 所回傳的 Stream 物件,裡面裝的已經不是 Person 物件了,而是字串,所以 forEach(Consumer) 的 Lambda Expression 中,e 代表的是字串,直接就可以顯示在 console 上。

最後,呼叫 showRank(persons, 700) 就可以看到曾經得超過 700 分的受測者排行榜了。整個流程可以畫成如 Figure 3的 pipeline。


Figure 3 - The pipeline for showRank

Figure 3 - The pipeline for showRank


老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,在 VisualTPL 中,迴圈的概念被內化了,重點在於做什麼運算 (what),而不是如何跑迴圈 (how),同樣地,Java Stream API 也是把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。不過 Java Stream API 的特色還不只這些,剩下的下一篇再討論。

avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
3/5高品質微服務
今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
5/5從 0 到 1
這陣子比較有空可以去天瓏書局晃晃,正好看到這本剛上市不久的書,整體上大多數守則,也是我自己一直在遵循的,是相當不錯的一本總結書。但真的要仔細看每一節的內容,理解每個原則背後的情境與想要改善的問題是什麼。如果只是把每一節的標題拿來使用,很容易就會發現衝突的部分。
5/5程式設計守則
今天完全沒有行程,就只有去機場搭飛機回台灣,飯店的自助式早餐依舊是很豐盛,甚至還有一區是可以自由組裝海鮮丼,我沒裝飯,只拿了幾樣海鮮。飛機是 11:05 起飛,所以,雖然離機場很近,提早兩小時到機場,意味著九點多該到機場了。
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
3/5高品質微服務
今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
5/5從 0 到 1
這陣子比較有空可以去天瓏書局晃晃,正好看到這本剛上市不久的書,整體上大多數守則,也是我自己一直在遵循的,是相當不錯的一本總結書。但真的要仔細看每一節的內容,理解每個原則背後的情境與想要改善的問題是什麼。如果只是把每一節的標題拿來使用,很容易就會發現衝突的部分。
5/5程式設計守則
今天完全沒有行程,就只有去機場搭飛機回台灣,飯店的自助式早餐依舊是很豐盛,甚至還有一區是可以自由組裝海鮮丼,我沒裝飯,只拿了幾樣海鮮。飛機是 11:05 起飛,所以,雖然離機場很近,提早兩小時到機場,意味著九點多該到機場了。
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節的目的是介紹Java中的套件使用,包括如何引用第三方套件和自定義模組,如何創建和使用自定義套件,以及介紹一些常見的Java標準庫套件。這些內容將幫助讀者更好地理解和使用Java的套件系統。
Thumbnail
本章節主要介紹Java語言中的函數(也稱為方法)的使用,包括函數的基本結構、函數表達式(Lambda表達式)、箭頭函數、匿名函數的使用,以及如何呼叫函數、如何使用函數參數和函數的返回值等內容。通過學習本章節,讀者將能夠熟練掌握Java語言中的函數相關知識,並能夠在實際編程中靈活運用。
Thumbnail
此章節的目的是介紹Java程式語言中的流程控制結構,包括條件語句(if, else if, else)、三元運算子、switch語句,以及各種迴圈(for, foreach, while)。同時,也解釋了如何在迴圈中使用控制語句來改變程式的執行流程。每種主題都配有示例程式碼以幫助理解。
Thumbnail
最近我人都泡在Threads上做社會觀察。 脆的演算法實在是太特別...
Thumbnail
此章節旨在介紹Java程式語言中的各種資料型別,包括基本型別、引用型別、集合型別、陣列型別、字典型別等。它還講解了如何在Java中進行型別轉換和自定義型別,並提供了相關的程式碼示例。
Thumbnail
此章節旨在介紹Java的基本語法、註解和變數的使用。透過學習,讀者將了解Java程式的基本結構、程式進入點的定義、如何撰寫單行和多行註解,以及如何宣告和初始化變數。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節的目的是介紹Java中的套件使用,包括如何引用第三方套件和自定義模組,如何創建和使用自定義套件,以及介紹一些常見的Java標準庫套件。這些內容將幫助讀者更好地理解和使用Java的套件系統。
Thumbnail
本章節主要介紹Java語言中的函數(也稱為方法)的使用,包括函數的基本結構、函數表達式(Lambda表達式)、箭頭函數、匿名函數的使用,以及如何呼叫函數、如何使用函數參數和函數的返回值等內容。通過學習本章節,讀者將能夠熟練掌握Java語言中的函數相關知識,並能夠在實際編程中靈活運用。
Thumbnail
此章節的目的是介紹Java程式語言中的流程控制結構,包括條件語句(if, else if, else)、三元運算子、switch語句,以及各種迴圈(for, foreach, while)。同時,也解釋了如何在迴圈中使用控制語句來改變程式的執行流程。每種主題都配有示例程式碼以幫助理解。
Thumbnail
最近我人都泡在Threads上做社會觀察。 脆的演算法實在是太特別...
Thumbnail
此章節旨在介紹Java程式語言中的各種資料型別,包括基本型別、引用型別、集合型別、陣列型別、字典型別等。它還講解了如何在Java中進行型別轉換和自定義型別,並提供了相關的程式碼示例。
Thumbnail
此章節旨在介紹Java的基本語法、註解和變數的使用。透過學習,讀者將了解Java程式的基本結構、程式進入點的定義、如何撰寫單行和多行註解,以及如何宣告和初始化變數。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio