本文為Java Stream的基礎教學文章,若有錯誤不吝指教。
Stream簡介
Stream是Java8的新特性,針對物件集合使用類似SQL語句從數據庫查詢數據,讓程式員得以乾淨、簡潔、高效率的代碼、達到聚合運算的目的。Stream主要分為兩種操作, intermediate operation及 terminal 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
物件,有兩點要特別注意。
- 在過濾特定性別時需要判斷字串是否一致,直覺上會想用
filter(person->person.gender=='Male')
,但最終結果不會是你想要的, 比較兩個String
變數時不可用==來判斷, 因為兩者的reference不同,因此要使用equals,判斷式將變成filter(person->person.gender.equals('Male'))
。 - 要取得新值最終一定要搭配terminal operations,如同本次
count()
方法會計算給定的Stream中有多少物件。
問題二:列出身高屬於離群值的人(三倍標準差)
首先利用自訂函式getStandardDeviation
取得heightList
身高列表並回傳標準差,然後用Stream中的filter
篩選距離平均身高三倍標準差的人,這裡有幾點值得留意。
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();