1. 收集器簡介
收集器用來將經過篩選、映射的流進行最後的整理,可以使得最後的結果以不同的形式展現。
collect方法即爲收集器,它接收Collector接口的實現作爲具體收集器的收集方法。
Collector接口提供了很多默認實現的方法,我們可以直接使用它們格式化流的結果;也可以自定義Collector接口的實現,從而定製自己的收集器。
這裏先介紹Collector常用默認靜態方法的使用,自定義收集器會在下一篇博文中介紹。
2. 收集器的使用
2.1 歸約
流由一個個元素組成,歸約就是將一個個元素“摺疊”成一個值,如求和、求最值、求平均值都是歸約操作。
2.1.1 計數
long count = list.stream()
.collect(Collectors.counting());
- 1
- 2
也可以不使用收集器的計數函數:
long count = list.stream().count();
- 1
注意:計數的結果一定是long類型。
2.1.2 最值
例:找出所有人中年齡最大的人
Optional<Person> oldPerson = list.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
- 1
- 2
計算最值需要使用Collector.maxBy和Collector.minBy,這兩個函數需要傳入一個比較器Comparator.comparingInt,這個比較器又要接收需要比較的字段。
這個收集器將會返回一個Optional類型的值。
Optional類簡介請移步至:Java8新特性——StreamAPI(一)
2.1.3 求和
例:計算所有人的年齡總和
int summing = list.stream()
.collect(Collectors.summingInt(Person::getAge));
- 1
- 2
當然,既然Java8提供了summingInt,那麼還提供了summingLong、summingDouble。
2.1.4 求平均值
例:計算所有人的年齡平均值
double avg = list.stream()
.collect(Collectors.averagingInt(Person::getAge));
- 1
- 2
注意:計算平均值時,不論計算對象是int、long、double,計算結果一定都是double。
2.1.5 一次性計算所有歸約操作
Collectors.summarizingInt函數能一次性將最值、均值、總和、元素個數全部計算出來,並存儲在對象IntSummaryStatisics中。
可以通過該對象的getXXX()函數獲取這些值。
2.1.6 連接字符串
例:將所有人的名字連接成一個字符串
String names = list.stream()
.collect(Collectors.joining());
- 1
- 2
每個字符串默認分隔符爲空格,若需要指定分隔符,則在joining中加入參數即可:
String names = list.stream()
.collect(Collectors.joining(", "));
- 1
- 2
此時字符串之間的分隔符爲逗號。
2.1.7 一般性的歸約操作
若你需要自定義一個歸約操作,那麼需要使用Collectors.reducing函數,該函數接收三個參數:
- 第一個參數爲歸約的初始值
- 第二個參數爲歸約操作進行的字段
- 第三個參數爲歸約操作的過程
例:計算所有人的年齡總和
Optional<Integer> sumAge = list.stream()
.collect(Collectors.reducing(0,Person::getAge,(i,j)->i+j));
- 1
- 2
上面例子中,reducing函數一共接收了三個參數:
- 第一個參數表示歸約的初始值。我們需要累加,因此初始值爲0
- 第二個參數表示需要進行歸約操作的字段。這裏我們對Person對象的age字段進行累加。
- 第三個參數表示歸約的過程。這個參數接收一個Lambda表達式,而且這個Lambda表達式一定擁有兩個參數,分別表示當前相鄰的兩個元素。由於我們需要累加,因此我們只需將相鄰的兩個元素加起來即可。
Collectors.reducing方法還提供了一個單參數的重載形式。
你只需傳一個歸約的操作過程給該方法即可(即第三個參數),其他兩個參數均使用默認值。
- 第一個參數默認爲流的第一個元素
- 第二個參數默認爲流的元素
這就意味着,當前流的元素類型爲數值類型,並且是你要進行歸約的對象。
例:採用單參數的reducing計算所有人的年齡總和
Optional<Integer> sumAge = list.stream()
.filter(Person::getAge)
.collect(Collectors.reducing((i,j)->i+j));
- 1
- 2
- 3
2.2 分組
分組就是將流中的元素按照指定類別進行劃分,類似於SQL語句中的GROUPBY。
2.2.1 一級分組
例:將所有人分爲老年人、中年人、青年人
Map<String,List<Person>> result = list.stream()
.collect(Collectors.groupingby((person)->{
if(person.getAge()>60)
return "老年人";
else if(person.getAge()>40)
return "中年人";
else
return "青年人";
}));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
groupingby函數接收一個Lambda表達式,該表達式返回String類型的字符串,groupingby會將當前流中的元素按照Lambda返回的字符串進行分組。
分組結果是一個Map< String,List< Person>>,Map的鍵就是組名,Map的值就是該組的Perosn集合。
2.2.2 多級分組
多級分組可以支持在完成一次分組後,分別對每個小組再進行分組。
使用具有兩個參數的groupingby重載方法即可實現多級分組。
- 第一個參數:一級分組的條件
- 第二個參數:一個新的groupingby函數,該函數包含二級分組的條件
例:將所有人分爲老年人、中年人、青年人,並且將每個小組再分成:男女兩組。
Map<String,Map<String,List<Person>>> result = list.stream()
.collect(Collectors.groupingby((person)->{
if(person.getAge()>60)
return "老年人";
else if(person.getAge()>40)
return "中年人";
else
return "青年人";
},
groupingby(Person::getSex)));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
此時會返回一個非常複雜的結果:Map< String,Map< String,List< Person>>>。
2.2.3 對分組進行統計
擁有兩個參數的groupingby函數不僅僅能夠實現多幾分組,還能對分組的結果進行統計。
例:統計每一組的人數
Map<String,Long> result = list.stream()
.collect(Collectors.groupingby((person)->{
if(person.getAge()>60)
return "老年人";
else if(person.getAge()>40)
return "中年人";
else
return "青年人";
},
counting()));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
此時會返回一個Map< String,Long>類型的map,該map的鍵爲組名,map的值爲該組的元素個數。
將收集器的結果轉換成另一種類型
當使用maxBy、minBy統計最值時,結果會封裝在Optional中,該類型是爲了避免流爲空時計算的結果也爲空的情況。在單獨使用maxBy、minBy函數時確實需要返回Optional類型,這樣能確保沒有空指針異常。然而當我們使用groupingBy進行分組時,若一個組爲空,則該組將不會被添加到Map中,從而Map中的所有值都不會是一個空集合。既然這樣,使用maxBy、minBy方法計算每一組的最值時,將結果封裝在optional對象中就顯得有些多餘。
我們可以使用collectingAndThen函數包裹maxBy、minBy,從而將maxBy、minBy返回的Optional對象進行轉換。
例:將所有人按性別劃分,並計算每組最大的年齡。
Map<String,Integer> map = list.stream()
.collect(groupingBy(Person::getSex,
collectingAndThen(
maxBy(comparingInt(Person::getAge)),
Optional::get
)));
- 1
- 2
- 3
- 4
- 5
- 6
此時返回的是一個Map< String,Integer>,String表示每組的組名(男、女),Integer爲每組最大的年齡。
如果不用collectingAndThen包裹maxBy,那麼最後返回的結果爲Map< String,Optional< Person>>。
使用collectingAndThen包裹maxBy後,首先會執行maxBy函數,該函數執行完後便會執行Optional::get,從而將Optional中的元素取出來。
2.3 分區
分區是分組的一種特殊情況,它只能分成true、false兩組。
分組使用partitioningBy方法,該方法接收一個Lambda表達式,該表達是必須返回boolean類型,partitioningBy方法會將Lambda返回結果爲true和false的元素各分成一組。
partitioningBy方法返回的結果爲Map< Boolean,List< T>>。
此外,partitioningBy方法和groupingBy方法一樣,也可以接收第二個參數,實現二級分區或對分區結果進行統計。