Java™ 教程(聚合概括操做)

聚合概括操做

聚合操做一節描述了下列操做管道,計算集合roster中全部男性成員的平均年齡:html

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

JDK包含許多終端操做(好比averagesumminmaxcount),它們經過組合流的內容返回一個值,這些操做稱爲概括操做。JDK還包含返回一個集合而不是單個值的概括操做,許多概括操做執行特定的任務,例如查找值的平均值或將元素分組到類別中。可是,JDK爲你提供了通用的reducecollect操做,本節將詳細介紹這些操做。java

你能夠在示例ReductionExamples中找到本節中描述的代碼摘錄。git

Stream.reduce方法

Stream.reduce方法是一種通用的概括操做,考慮如下管道,它計算集合roster中男性成員的年齡總和,它使用Stream.sum概括操做。github

Integer totalAge = roster
    .stream()
    .mapToInt(Person::getAge)
    .sum();

將其與下面使用流的管道進行比較,使用Stream.reduce操做計算相同的值:segmentfault

Integer totalAgeReduce = roster
   .stream()
   .map(Person::getAge)
   .reduce(
       0,
       (a, b) -> a + b);

本例中的reduce操做有兩個參數:api

  • identity:標識元素既是概括的初始值,也是流中沒有元素時的默認結果,在本例中,標示元素爲0,這是年齡總和的初始值,若是集合roster中沒有成員,則爲默認值。
  • accumulator:累加器函數接受兩個參數:概括的部分結果(在本例中,是到目前爲止全部處理過的整數的和)和流的下一個元素(在本例中,是一個整數),它返回一個新的局部結果。在本例中,累加器函數是一個lambda表達式,它添加兩個Integer值並返回一個Integer值:(a, b) -> a + b

    reduce操做老是返回一個新值,可是,accumulator函數每次處理流的元素時也會返回一個新值,假設你但願將流的元素概括爲更復雜的對象,例如集合,這可能會影響應用程序的性能。若是reduce操做涉及向集合添加元素,那麼每次累加器函數處理一個元素時,它都會建立一個包含該元素的新集合,這是低效的,相反,更新現有的集合將更有效,你可使用Stream.collect來實現這一點。

Stream.collect方法

reduce方法不一樣,collect方法修改或改變現有值,而reduce方法在處理元素時老是建立一個新值。oracle

考慮如何找到流中值的平均值,你須要兩段數據:值的總數和這些值的和。然而,與reduce方法和全部其餘概括方法同樣,collect方法只返回一個值,你能夠建立一個包含成員變量的新數據類型,該成員變量跟蹤值的總數和這些值的總和,例以下面的類Averagerapp

class Averager implements IntConsumer {
    private int total = 0;
    private int count = 0;
        
    public double average() {
        return count > 0 ? ((double) total)/count : 0;
    }
        
    public void accept(int i) { total += i; count++; }
    public void combine(Averager other) {
        total += other.total;
        count += other.count;
    }
}

下面的管道使用Averager類和collect方法計算全部男性成員的平均年齡:ide

Averager averageCollect = roster.stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(Person::getAge)
    .collect(Averager::new, Averager::accept, Averager::combine);
                   
System.out.println("Average age of male members: " +
    averageCollect.average());

本例中的collect操做接受三個參數:函數

  • suppliersupplier是個工廠方法,它構造新的實例,對於collect操做,它建立結果容器的實例,在本例中,它是Averager類的一個新實例。
  • accumulatoraccumulator函數將流元素合併到結果容器中,在本例中,它經過將count變量增長1,並將流元素的值添加到total成員變量中,該元素是一個整數,表示男性成員的年齡,來修改Averager結果容器。
  • combinercombiner函數接受兩個結果容器併合並它們的內容,在本例中,它經過將count變量與另外一個Averager實例的count成員變量相加,並將另外一個Averager實例的total成員變量的值添加到total成員變量中,從而修改Averager結果容器。

請注意如下:

  • supplier是lambda表達式(或方法引用),而不是reduce操做中的identity元素之類的值。
  • accumulatorcombiner函數不返回值。
  • 你可使用並行流的collect操做(若是你使用並行流運行collect方法,那麼每當combiner函數建立一個新對象時,JDK都會建立一個新線程,例如本例中的Averager對象,所以,你沒必要擔憂同步)。

雖然JDK提供了計算流中元素平均值的average操做,可是若是須要從流的元素中計算多個值,可使用collect操做和自定義類。

collect操做最適合於集合,下面的示例使用collect操做將男性成員的名稱放入集合中:

List<String> namesOfMaleMembersCollect = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());

這個版本的collect操做只接受Collector類型的一個參數,該類封裝了collect操做中用做參數的函數,該操做須要三個參數(supplieraccumulatorcombiner函數)。

Collectors類包含許多有用的概括操做,好比將元素累積到集合中,並根據各類標準彙總元素,這些概括操做返回類Collector的實例,所以能夠將它們用做collect操做的參數。

本例使用Collectors.toList操做,它將流元素累積到List的新實例中,與Collectors類中的大多數操做同樣,toList操做符返回Collector的實例,而不是集合。

如下示例按性別將集合roster的成員分組:

Map<Person.Sex, List<Person>> byGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(Person::getGender));

groupingBy操做返回一個map,其鍵是應用指定爲其參數的lambda表達式(稱爲分類函數)所獲得的值。在本例中,返回的map包含兩個鍵,Person.Sex.MALEPerson.Sex.FEMALE,鍵對應的值是List的實例,其中包含流元素,當分類函數處理這些元素時,這些元素與鍵值對應。例如,與鍵Person.Sex.MALE對應的值是一個包含全部男性成員的List實例。

如下示例檢索集合roster中每一個成員的姓名,並按性別將其分組:

Map<Person.Sex, List<String>> namesByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.mapping(
                    Person::getName,
                    Collectors.toList())));

本例中的groupingBy操做接受兩個參數,一個分類函數和一個Collector實例,Collector參數稱爲下游收集器,這是Java運行時應用於另外一個收集器的結果的收集器。所以,這個groupingBy操做使你可以對groupingBy操做符建立的List值應用collect方法。此示例應用收集器mapping,它將mapping函數Person::getName應用於流的每一個元素。所以,產生的流只包含成員的名稱,包含一個或多個下游收集器的管道(如本例)稱爲多級概括。

下面的示例檢索每種性別成員的總年齡:

Map<Person.Sex, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));

reducing操做須要三個參數:

  • identity:如Stream.reduce操做,若是流中沒有元素,則identity元素既是概括的初始值,也是缺省結果,在這個例子中,identity元素是0,這是年齡總和的初始值,若是不存在成員,則爲默認值。
  • mapperreducing操做將此mapper函數應用於全部流元素,在本例中,mapper檢索每一個成員的年齡。
  • operationoperation函數用於概括映射值,在本例中,operation函數添加Integer值。

下面的例子檢索了每種性別成員的平均年齡:

Map<Person.Sex, Double> averageAgeByGender = roster
    .stream()
    .collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.averagingInt(Person::getAge)));

上一篇:聚合操做

相關文章
相關標籤/搜索