Stream 學習筆記(中)

開發(fā)環(huán)境
  • eclipse 4.7.3a
  • jdk 10
前置知識點
關(guān)于 Reduction

Stream包含許多最終操作,比如: average、sum、min、max、count,它們通過通過組合流的內(nèi)容返回一個值。這些操作我們暫時稱之為 “統(tǒng)計操作“(Reduction)。

下面我們來分別介紹兩種不同的 “統(tǒng)計操作“ 方式:

  • Stream.reduce
  • Stream.collect

關(guān)于Reduction的命名,有更好的提議可以留言告知。

Stream.reduce 方法

如下所示,該管道計算集合中男性成員年齡的總和,它使用 Stream.sum 方法來進行 “統(tǒng)計操作“

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

使用Stream.reduce操作來計算相同的值

roster.stream().map(Person::getAge).reduce(0, (a, b) -> a + b).intValue();
Stream.reduce方法包含兩個參數(shù):
  • identity:“統(tǒng)計操作“ 的結(jié)果,如果流中沒有元素,則identity元素既是初始值又是默認結(jié)果。
  • accumulator: 累加器函數(shù)有兩個參數(shù):統(tǒng)計的部分結(jié)果(在本例中,到目前為止所有處理過的整數(shù)的總和)和流的下一個元素(在本例中為整數(shù)), 它返回一個新的中間值。 在此示例中,accumulator函數(shù)是一個lambda表達式,它添加兩個Integer值并返回一個Integer值。

如果reduce操作涉及向集合添加元素,那么每次accumulator函數(shù)處理元素時,它都會創(chuàng)建一個包含元素的新集合,這可能會影響性能。

Stream.collect 方法

與reduce方法不同,reduce方法在處理元素時始終創(chuàng)建新值,而collect方法修改或改變現(xiàn)有值。
考慮如何計算平均值,需要兩類數(shù)據(jù):總記錄數(shù)和記錄的總和。collect方法只創(chuàng)建一次新值,用來跟蹤總記錄數(shù)和記錄的總和,例如以下類Averager:

static 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;
      }
 }

如下所示,該管道使用collect方法計算集合中男性成員年齡的總和

// collect
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());
Stream.collect 方法包含三個參數(shù):
  • supplier: 工廠對象,返回結(jié)果容器中,用來存儲計算的中間結(jié)果。在此示例中,返回的是Averager的實例。
  • accumulator:累加器,函數(shù)將流元素合并到結(jié)果容器中。 在此示例中,它通過將count變量遞增1并將total元素的值進行累加,并保存到Averager結(jié)果容器中。
  • combiner: 組合器函數(shù)接受多個結(jié)果容器并合并其內(nèi)容,如果存在的話。
Stream.collect 方法的其它用例
過濾結(jié)果集
List<Integer> list = roster.stream()
.filter(p -> p.getGender() == Person.Sex.MALE). map(Person::getAge).collect(Collectors.toList());
按關(guān)鍵字分組
Map<Person.Sex, List<Person>> byGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(Person::getGender));
分組匯總
Map<Person.Sex, Double> averageAgeByGender = roster
    .stream()
    .collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.averagingInt(Person::getAge)));
源碼學習

查看下面代碼的完整調(diào)用示例

Map<Person.Sex, Double> averageAgeByGender0 = roster.stream()
                .collect(Collectors.groupingBy(Person::getGender, Collectors.averagingInt(Person::getAge)));
Collector 部分源碼
public interface Collector<T, A, R> {

    Supplier<A> supplier();

    BiConsumer<A, T> accumulator();

    BinaryOperator<A> combiner();

    Function<A, R> finisher();
}
通過Collector接口聲明,我們可以看到幾個關(guān)鍵的方法申明:
  • supplier:結(jié)果容器工廠方法
  • accumulator:累加器工廠方法,返回累加器實例
  • combiner:組合函數(shù)工廠方法,返回組合函數(shù)接口實例
  • finisher:終止操作工廠方法,返回終止操作函數(shù)接口實例
泛型說明
  • T:參與累加器計算的對象類型
  • A:結(jié)果容器對象類型
  • R : 終止操作返回的結(jié)果類型
Collectors.averagingInt 源碼
public static <T> Collector<T, ?, Double>
    averagingInt(ToIntFunction<? super T> mapper) {
        return new CollectorImpl<>(
                () -> new long[2],
                (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; },
                (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
                a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);
}
通過Collector接口可知,Collectors.averagingInt方法是如何實現(xiàn)求平均值的
  1. 創(chuàng)建long數(shù)組保存中間計算結(jié)果
  2. 調(diào)用accumulator方法進行累計,并把中間結(jié)果存儲到long數(shù)組中
  3. 如果需要合并多個中間結(jié)果,則把兩個元素的結(jié)果進行匯總保存到第一個流元素中
  4. 計算并得到平均值
Collectors.groupingBy源碼
public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {
        Supplier<A> downstreamSupplier = downstream.supplier();
        BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
        BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
            K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
            A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
            downstreamAccumulator.accept(container, t);
        };
        BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
        @SuppressWarnings("unchecked")
        Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;

        if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
            return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
        }
        else {
            @SuppressWarnings("unchecked")
            Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
            Function<Map<K, A>, M> finisher = intermediate -> {
                intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
                @SuppressWarnings("unchecked")
                M castResult = (M) intermediate;
                return castResult;
            };
            return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
        }
}
源碼剖析

了解相應(yīng)泛型對應(yīng)本例的類型

  • T:Person對象
  • K:Person.Sex
  • D:Double
  • A:long[]
  • M:Map<Person.Sex, Double>
探討部分

探討點一

# 入?yún)⒉糠?Supplier<M> mapFactory

# 后續(xù)又進行了強制類型轉(zhuǎn)換
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;

從源碼可以看出 mapFactory工廠方法應(yīng)返回Map<Person.Sex, Long[]> 對象,所以上述方法參數(shù)是否為如下會更加明了

public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<K, A> mapFactory,
                                  Collector<? super T, A, D> downstream)

探討點二

Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
Function<Map<K, A>, M> finisher = intermediate -> {
    intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
    @SuppressWarnings("unchecked")
    M castResult = (M) intermediate;
    return castResult;
};

源碼中終止方法又進行了兩次強制類型轉(zhuǎn)換

  1. 為了調(diào)用intermediate.replaceAll,對downstream方法進行了一次強制類型轉(zhuǎn)換
  2. 為了返回類型約束的結(jié)果對M進行了一次強制類型轉(zhuǎn)換

重構(gòu)如下

# 修改方法的聲明
public static <T, K, D, A> Collector<T, ?, Map<K, D>> groupingBy(....)

# 修改結(jié)果返回函數(shù)
@SuppressWarnings("unchecked")
Function<Map<K, A>, Map<K, D>> finisher = intermediate ->
{
    Map<K, D> castResult = new HashMap<>();

    intermediate.entrySet().stream()
        .forEach(t -> castResult.put(t.getKey(), downstream.finisher().apply(t.getValue())));
         return castResult;
};

以上僅為個人觀點,水平有限,歡迎大家指正。

關(guān)于泛型,它的初衷是讓具有強制類型轉(zhuǎn)換的代碼具有更好的安全性和可讀性。

Github工程地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Int Double Long 設(shè)置特定的stream類型, 提高性能,增加特定的函數(shù) 無存儲。stream不是一...
    patrick002閱讀 1,321評論 0 0
  • Jav8中,在核心類庫中引入了新的概念,流(Stream)。流使得程序媛們得以站在更高的抽象層次上對集合進行操作。...
    仁昌居士閱讀 4,062評論 0 6
  • 本文采用實例驅(qū)動的方式,對JAVA8的stream API進行一個深入的介紹。雖然JAVA8中的stream AP...
    浮梁翁閱讀 26,111評論 3 50
  • 1. Stream初體驗 我們先來看看Java里面是怎么定義Stream的: A sequence of elem...
    kechao8485閱讀 1,279評論 0 9
  • 詞:許逍 還記得那年嗎? 你送我的背包 我們的相遇 平淡無奇 我們的相識 獨一無二 離開后的我 你的背包成了我唯一...
    許逍閱讀 298評論 0 0

友情鏈接更多精彩內(nèi)容