Java8-Collectors.groupingBy()-JDK源碼分析

1.引子

groupingBy方法有多個重載方法,但是根本上只有一個方法。之所以提供這麼多方法的重載,主要目的還是爲了開發者調用方便。通過對於此分組靜態方法的學習,我們可以更好地瞭解Java在收集器collector接口實現上的設計模式以及設計思想。

2.源碼分析

CodeBlock-1:

            public static <T, K> Collector<T, ?, Map<K, List<T>>>
            groupingBy(Function<? super T, ? extends K> classifier) {
                return groupingBy(classifier, toList());
            }

 這是groupingBy方法的重載版本之一,實際上其調用了重載版本2的代碼塊,即 CodeBlock-2: 其輸入參數只有分類器接口的實例,所以中間容器被默認限制爲ArrayList類型。

CodeBlock-2:

    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

 這是groupingBy方法的第二個重載版本,其輸入參數有分類器實例classifier,下流收集器實例downstream,其實際上調用了 CodeBlock-3: 中的第三個重載版本,所以具體如何實現的我們放到 CodeBlock-3: 中進行分析。

CodeBlock-3:
注意: 我幾乎對源碼每句/塊語句都進行了分析,而分析的註釋都是在語句/塊的下方。

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(); 
                //D得到下流收集器的supplier對象,其提供了下流收集器的結果容器類型A
                BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
                //獲得下流收集器的累加器接口實現對象。
                BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {

//不加任何前綴的accumulator接口實現對象是指整個groupingBy方法返回的收集器的accumulator接口實現對象,
//其通過classifier(Function)、downstream(Collector)一起來構建的:構建的累加器使中間結果類型爲:Map<K,A>,

                    K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
                    //得到一個對象的鍵值,且規定鍵值不能爲空
                    A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
//這是A類型的中間容器,我們對鍵值進行判斷,如果存在與key對應的值A,即A類中間結果容器不爲null,
//則返回其對應(注意:這裏已經是返回元素所對應分類組別的中間容器,所以每個元素都要進行一次累加操作)的中間結果容器A(Map<K, A>),
//如果沒有對應的結果容器,則返回一個新的空結果容器,並將key值和新的結果容器放入此map中,並且返回這個結果容器。
                    downstreamAccumulator.accept(container, t); 
//這裏調用了下流傳遞過來的累加方法,可見其實現的操作是將元素添加到經根據Key值分類的中間結果容器中。
                };
                BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
//此方法主要作用是合併兩個map,並處理重複鍵
                @SuppressWarnings("unchecked")
                Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
//這裏將輸入參數中的supplier對象強制類型轉換能夠一定保證不報錯,這是因爲整個分組方法的中間結果容器類型就是Supplier<Map<K, A>>類型,
//而參數中的supplier接口目的本身就是如此,提供整個接口實現的中間結果容器。
//而且注意,這裏只是說傳遞給新的引用變量,使mangledFactory作爲整個方法的返回的接口實例中的supplier實現。
//還有一個深層次原因,因爲Supplier<Map<K, A>>和最終結果容器Supplier<Map<K, D>>,
//其實就是差一步finisher的操作,所以強制類型轉換就是把Value值改變了一下,但是不影響map這個整體框架。
//所有強制類型轉換真的就是真的只是類型轉換所以纔可以轉。

                if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
                    return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
                }
//如果此方法返回的collector接口有Collector.Characteristics.IDENTITY_FINISH特性,那麼就跳過finisher,
//直接返回累加器就結束了,最終結果容器類型爲:Map<K, A>
                else {
                    @SuppressWarnings("unchecked")
                    Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
//強制類型轉換能夠實現的理由還是A->D也就一個finisher操作,map的框架並沒有改變
                    Function<Map<K, A>, M> finisher = intermediate -> { 
//其實此處的目的很直接,就是實現整個分組方法的finisher接口實現,從Map<K, A>轉變爲M extends Map<K, D>
                        intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
//replaceAll方法是Map類的方法,其方法就是保留key,但是將value通過Function接口實現替換掉功能,這裏的語句當然不是馬上執行,
//而是爲了實現finisher接口穿進去lambda表達式而已,目前爲止已經實現Map<K, A>到Map<K,D>的轉化,但是不要急於返回。
                        @SuppressWarnings("unchecked")
                        M castResult = (M) intermediate;
//這所以要強制類型磚轉換,這是因爲分組方法的總體返回是M extends Map<K, D>,而不是Map<K, D>,
//當然如果M extends Map<K, D> 不對,就會產生強制類型轉換的錯誤
                        return castResult;
                    };
                    return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
//可見最終的分組也是返回一個collector對象實現,只不過其內部邏輯比一般的數據結構的簡單轉換更爲複雜。
                }
            }

3. 源碼的設計模式與思想分析

 如果你覺得上述分析有難度,不妨先看這裏的設計思想在返回2中進行源碼閱讀,又或者你lambda表達式和方法引用還未學習,在看完設計模式後,在看看我寫關於Lambda表達式以及方法引用的博文,在回來看2中的源碼也是一個循序漸進的好選擇。

  1. 對每個元素使用classifier(Function)找到當前元素所對應的鍵值,用於分類
    1). 如果當前鍵值對應的組別已有中間結果容器A,那麼就將當前元素加入此中間結果容器A中,並且將中間結果容器放到Map<K,A>
    2) 如果沒有,則創建與當前元素鍵值對應的中間結果容器,並將當前元素放入此容器中;
  2. 分類好了 形成中間結果容器map<K,A>->然後如果有多線程的話,就進行對個map對象的合併,否則無序調用合併器的方法
  3. 如果沒有特性Characteristics.IDENTITY,那麼就調用finisher接口進行中間結果容器的類型轉化map<K,D>,最後強制類型轉換爲Map<K,M>返回

以上泛型符號類型說明:

T:流中的單個元素類型
K:元素進行分類的屬性,最終Map數據結構的鍵值Key
D:downstrream下流收集器對象的最終結果容器
A:downstrream下流收集器對象的中間結果容器
M:M extends Map<K, D>,其爲整個分類方法的中間結果容器

源碼中接重要收集器接口對象的作用說明:

1)classifier提供一個接口實現,從對象中提取屬性,返回鍵值K:算是從T類型的元素對象中提取相關的屬性值K,用來進行分組、分類的判斷依據 此方法針對於流的單個元素
2)mapFactory提供一個最大的框架M:抽象接口Map的實現:HashMap或者TreeMap    此方法是無參的!
3)downstream提供了一個接口與實現,提供一種方法:可將相同分類組的對象用一個結果容器存放,並返回D                                         此方法也針對流的單個元素

流中處理元素的核心思想:
 流的操作都是Lazy的,不可能像一批產品的流水線一樣完成(畢竟工人,即處理器核心數,沒有那麼多),而是一個元素進行一整套完整的工作流程,至少一步到位地運行到將其放到方法整體的中間結果容器中。

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