圖片來源:Java 8 Stream — Java Stream

Java的 Stream基礎觀念及簡單實例。

邱秉誠
6 min readNov 16, 2019

本文為Java Stream的基礎教學文章,若有錯誤不吝指教。

Stream簡介

Stream是Java8的新特性,針對物件集合使用類似SQL語句從數據庫查詢數據,讓程式員得以乾淨、簡潔、高效率的代碼、達到聚合運算的目的。Stream主要分為兩種操作, intermediate operationterminal operations,前者常見方法有filter, map, sorted,是屬於惰性操作,可以想像成是資料的處理過程,程式執行到此不會產生新值,需要有後續的terminal operations,常見的方法有 collect、 forEach、 reduce屬於急性操作,將前段處理後的資料進行收集的動作。區分惰性與急性的原因在於因應複雜操作的需求,我們往往會建構一系列串接的惰性操作,而最後有個急性操作產生最後結果。以下將逐一介紹各種方法,並在文章後段輔以簡單實例。

Stream方法

  • filter:透過設定的條件過濾元素。
List<String>strings = Arrays.asList("小狗", "", "小貓", "小豬", "小鳥","");
// 獲取非空字串數量
long animalCount = strings.stream().filter(string -> !string.isEmpty()).count();
  • map:映射每个元素到對應的結果。
List<Double> numbers = Arrays.asList(4.0, 9.0, 16.0, 25.0, 36.0, 49.0);
//將每個元素開根號
List<Double> sqrtList = numbers.stream().map( num -> Math.sqrt(num)).collect(Collectors.toList());
  • forEach: 迭代Stream中的每個元素。
List<String> people = Arrays.asList("小明", "小王", "小呆","小誠");
people.stream().forEach(System.out::println);
  • reduce: 在群集中透過連接動作將元素匯總成單一結果。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
//將列表元素加總
Integer sum = integers.stream().reduce(0, (a, b) -> a + b);

實例應用

以下文章將透過問題導向的方式來學習如何靈活應用Stream方法。完整代碼展示在文章末段。

資料集

一份紀載性別身高體重的資料,以csv格式儲存。

預處理

以BufferedReader逐行讀取csv檔案,一筆紀錄便是一個Person物件,裝納在名為 personArrayList的ArrayList集合中,細節在此省略,再次提醒完整代碼展示在文章末段。

問題一:列出男女性別人數統計

這題十分單純直覺,使用filter設定男女條件過濾對應的Person物件,有兩點要特別注意。

  1. 在過濾特定性別時需要判斷字串是否一致,直覺上會想用filter(person->person.gender=='Male'),但最終結果不會是你想要的, 比較兩個String變數時不可用==來判斷, 因為兩者的reference不同,因此要使用equals,判斷式將變成filter(person->person.gender.equals('Male'))
  2. 要取得新值最終一定要搭配terminal operations,如同本次count() 方法會計算給定的Stream中有多少物件。

問題二:列出身高屬於離群值的人(三倍標準差)

首先利用自訂函式getStandardDeviation 取得heightList身高列表並回傳標準差,然後用Stream中的filter 篩選距離平均身高三倍標準差的人,這裡有幾點值得留意。

  1. getStandardDeviation 的函式中使用reduce((double) 0, (a, b) -> a + Math.pow(b — mean, 2))方法求得離均差平方和( sum of squares of deviation from mean),還記得reduce的功能嗎?在群集中透過連接動作將元素匯總成單一結果。這裡的a表示累積器的目前結果,b則是逐一迭代的元素,這意味著我從第一個元素開始使用Math.pow(b — mean, 2) 計算,並將結果相加(初始值為0)歸納進累積器a,第二個元素使用Math.pow(b — mean, 2) 計算,並將結果與歸納進累積器a,如此迭代運算至最後一個元素,最終返回累積器的值,在這個例子中扮演如同數學 求和符號Σ,sigma)的作用。
//ss => sum of squares of deviation from mean
double ss = data.stream().reduce((double) 0, (a, b) -> a + Math.pow(b - mean, 2));
return Math.sqrt(std / (count - 1));

2. DoubleStream的average()方法是回傳 OptionalDouble型態的變數,因此如果想要維持double型態的話,最後都要補上getAsDouble(),java的型態檢查是出名的嚴格,要特別留意型態是否吻合。

personArrayList.stream().mapToDouble(person -> person.height).average().getAsDouble();

完整實例

參考資料

  1. https://www.geeksforgeeks.org/stream-in-java/
  2. https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
  3. https://www.geeksforgeeks.org/stream-in-java/

--

--

邱秉誠
邱秉誠

Written by 邱秉誠

畢業於台大工業工程所,目前任職於台積電。

No responses yet