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的,不可能像一批产品的流水线一样完成(毕竟工人,即处理器核心数,没有那么多),而是一个元素进行一整套完整的工作流程,至少一步到位地运行到将其放到方法整体的中间结果容器中。

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