Java8新特性——StreamAPI(二)

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方法一樣,也可以接收第二個參數,實現二級分區或對分區結果進行統計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章