StreamAPI源码分析之四(Collectors工厂类方法深入刨析上篇)

前言

上一小节基本的函数式接口以及函数式思维进行了讲解,为了本节进行基础打底,当然这个是必要的,这里面的静态方法还是有些理解难度的(某几个),本节将对Collectors工厂类方法一部分方法进行分析,由于方法较多,所以需要分开进行细致的刨析。ok,长话短说,进行今天的分析之旅吧。

一、Collectors工厂类私有的静态方法

  • throwingMerger()
private static <T> BinaryOperator<T> throwingMerger() {
     
        return (u,v) -> {
      throw new IllegalStateException(String.format("Duplicate key %s", u)); };
    }

返回一个合并函数,适合在Map的merge(Object,Object,BiFunction)Map.merge()}或toMap(function,function,BinaryOperator)toMap()}中使用,该函数始终抛出 IllegalStateException。这可以用来强制假设所收集的元素是不同的。
该代码可以理解为如此:

BinaryOperator<T> binaryOperator=(u,v)
 -> {
      throw new IllegalStateException(String.format("Duplicate key %s", u)); };

其实就是一个实现了带调用的变量,当然函数式称之为行为,下面还有基本都是如此的静态方法,就不一一这样每一个都举例出变量的形式来理解了,分析方式一致,与 throwingMerger();

  • castingIdentity()
private static <I, R> Function<I, R> castingIdentity() {
     
        return i -> (R) i;
    }

此方法将泛型I强制转换成R,当然在转换之前需要进行类型检验,以免发生强制类型转换异常;

  • mapMerger(BinaryOperator mergeFunction)
private static <K, V, M extends Map<K,V>>
    BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
     
        return (m1, m2) -> {
     
            for (Map.Entry<K,V> e : m2.entrySet())
                m1.merge(e.getKey(), e.getValue(), mergeFunction);
            return m1;
        };
    }

这里需要进行转换分析,这是一个比上面来说更复杂的高阶函数,当然这里还有没有进行函数复合,只是一个函数式接口实现,等价为如下代码:

BinaryOperator<M>  binaryOperator=(m1, m2) -> {
     
            for (Map.Entry<K,V> e : m2.entrySet()){
     
                m1.merge(e.getKey(), e.getValue(), mergeFunction);
            }
            return m1;
        };

首先先分析BinaryOperator,接口内唯一的抽象方法R apply(T t, U u);接收两个参数,返回一个参数(当然这里i就是m1,如果binaryOperator被执行的话),当然,代码实现里进行了mergeFunction函数返回值的切割,结果会在m1里面并进行返回操作。

  • boxSupplier(T identity)
private static <T> Supplier<T[]> boxSupplier(T identity) {
     
        return () -> (T[]) new Object[] {
      identity };
    }

将identity放入到T[]里面,生成T类型的数组,但是泛型不可以直接创建数组,所以需要new Object[]生成出来,在进行强转。

二、Collector的第三个泛型返回值集合类型的静态方法

toList()

public static <T>
    Collector<T, ?, List<T>> toList() {
     
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> {
      left.addAll(right); return left; },
                                   CH_ID);
    }

方法返回一个 Collector,它将输入元素累加到一个新的 List中。对于返回的 List的类型、可变性、serializability或线程安全性没有保证;如果需要对返回的List进行更多控制,请使用oCollection(Supplier)。
返回值为收集器接口Collector(收集器的实现的返回参数为集合)的静态方法基本都是这样的实现思路,所以就以第一个开刀进行刨析,我会分层给大家分析清楚这块代码究竟做了什么?

StreamAPI源码分析之二(Collectors工厂类内部设计分析篇)链接,我们介绍了Collectors内部设计,其中静态内部类CollectorImpl进行了分析,也是对本节的基础打底,如果没有看过这一篇的可以补充下基础知识,现在理解方法觉得没有理解苦恼的,我们继续往下分析。
这里new CollectorImpl<>是静态内部类在当前类里的静态方法里的创建方式,而CollectorImpl本来有两个构造方法(四参和五参),这里使用的是四参的构造函数。
现在对构造函数中的每个参数的实现方式进行分析:

  • 这里是将生成的新容器赋值给CollectorImpl的Supplier提供商接口,(Supplier) ArrayList::new:通过方法引用(lambda一种具体的实现,没有箭头参数传入的方式)的形式实现Supplier接口,这里等价于代码片段;
ArrayList::new;//方法引用的形式
()->{
     return new ArrayList();}//lambda形式
两个是等价的
  • 将实现的累加具体操作,赋值给CollectorImpl的accumulator累加器接口 ,List::add:累加具体操作;
List::add;//方法引用的形式
(item,list)->{
     list.add(item);}//lambda形式
两者是等价的,只是方法引用的形式隐藏了一些细节
  • 将实现的合并操作赋值给CollectorImpl的combiner合并器接口, (left, right) -> { left.addAll(right); return left; }:具体的合并操作细节,而CollectorImpl的赋值参数为BinaryOperator,是BiFunction 的子类,所以两个输入类型和一个返回类型都为A类型,在这里也就是List
 (left, right) -> {
      left.addAll(right); return left; }://lambda形式
 它的代替形式
 List::addAll;//方法引用的形式
 两者等价(如果addAll返回处理完成的集合的话,但是这里不是集合所以这里方法引用行不通)
 但是List的addAll返回值是boolean,这里需要返回一个集合本身,所以这里不能直接使用方法引用,所以源码使用的是lambda形式的
  • 将实际设置给集合的特性,放到了setj不可变集合里,赋值给CollectorImpl的characteristics参数, CH_ID:是设置IDENTITY_FINISH特性的set集合,当设置了此参数,在Stream流计算过程中会对中间处理容器和最终返回类型集合进行强制类型转换,在后面深入剖析Stream流计算的源码过程中,会给大家节揭示这秘密的一角。

toList()函数在这里就分析完了,但是方法体可以这样理解:

public static <T>
    Collector<T, ?, List<T>> toList() {
     
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> {
      left.addAll(right); return left; },
                                   CH_ID);
    }

与下面这个是等价的,都是对Collector进行了实现,只是都还没有被调用而已,这里只是说调用这个toList()方法与collector没啥区别而已;

Collector<T, ?, List<T>> collector=new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> {
      left.addAll(right); return left; },
                                   CH_ID);

接下的一些和 toList()类似,就不费大量的时间来分析了,分析思路一致。

toSet()

return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                                   (left, right) -> {
      left.addAll(right); return left; },
                                   CH_UNORDERED_ID);

返回一个Collector,它将输入元素累加到一个新的Set。对于返回的Set的类型、可变性、serializability或线程安全性没有保证;
简要分析:

  • 这里不一样的可能就是使用的集合变成了HashSet
  • 最后一个参数变成了CH_UNORDERED_ID(具备Stream 的 UNORDERED无序和IDENTITY_FINISH标识完成特性)

toMap

public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
     
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

当然还有两个重载的toMap方法,但是两个最终还是都是调用了上面的这个toMap方法,此为toMap的具体实现细节。

  • CollectorImpl的第一个参数:mapSupplier是外部传入进来的Supplier函数接口,泛型将会是Collector 最终返回的集合类型,当然也在泛型方法中定义了M是M extends Map,Map的子类。
  • CollectorImpl的第二个参数:accumulator在toMap内部声明出来,而accumulator具体实现中,做了map.merge操作(这里就是实现keyMapper的Function和valueMapper的Function都调用完apply(element)返回值),这个map.merge方法就是当有了这个key以后和没有key怎么处理,当然需要mergeFunction受到策略的影响,在下面的代码中remappingFunction.apply(oldValue, value)对新旧value进行了选择选择性处理,这个就看我们需要的策略是什么了,在实际的应用中。
/*
* @since 1.8
     */
    default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
     
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
        if(newValue == null) {
     
            remove(key);
        } else {
     
            put(key, newValue);
        }
        return newValue;
    }

这个是JDK1.8Map新增的默认方法,它的作用就是将传入的key获取到它的值

1、如果oldValue 不存在,就是用参数传入的value,如果newValue 被value赋值后,依然为空,就将key移除,
或者为key设置新的value
2、如果oldValue 存在,就好执行mergeFunction函数的apply方法,处理oldValue以及新传入的value,执行结果要看传入
的BiFunction具体实现了,BiFunction是我们外部调用者可控制的。
  • CollectorImpl的第三个参数: mapMerger(mergeFunction),方法,代码如下:
private static <K, V, M extends Map<K,V>>
    BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
     
        return (m1, m2) -> {
     
            for (Map.Entry<K,V> e : m2.entrySet())
                m1.merge(e.getKey(), e.getValue(), mergeFunction);
            return m1;
        };
    }

上面的源码可以理解为这样的变形,调用这个方法mapMerger(BinaryOperator mergeFunction)等于下面:

 BinaryOperator<M> mapMerger=(m1, m2) -> {
     
            for (Map.Entry<K,V> e : m2.entrySet())
                m1.merge(e.getKey(), e.getValue(), mergeFunction);
            return m1;

重点就是M泛型就是我们需要返回的元素,也就是最终返回的集合类型,而上面的方法中,BinaryOperator的父类BiFunction的R apply(T t, U u);的方法都是一样的参数T此刻,这里的用意就是将m2集合的参数放入到m1集合中,然后更加我们设定的mergeFunction合并函数策略(toMap默认策略是抛出异常->throwingMerger()),最后就将m2的所有与m1不重复的key的内容放进去了.

  • CollectorImpl的第四个参数:CH_ID:是设置IDENTITY_FINISH特性的set集合,当设置了此参数,在Stream流计算过程中会对中间处理容器和最终返回类型集合进行强制类型转换,与toSet()一致。

这个时候收集器的实现就已经完成了,等待调用了可以。

toConcurrentMap

public static <T, K, U, M extends ConcurrentMap<K, U>>
    Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
                                       Function<? super T, ? extends U> valueMapper,
                                       BinaryOperator<U> mergeFunction,
                                       Supplier<M> mapSupplier) {
     
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_CONCURRENT_ID);
    }

返回一个并发的 Collector,它将元素累加到@code ConcurrentMap中,该映射的键和值是将提供的映射函数应用于输入元素的结果。
toConcurrentMap与toMap在CollectorImpl的第二个参数、第三个参数是一模一样的实现方式,就不多做分析了,这里说一下第一个参数和最后一个参数.。

  • mapSupplier:一定是ConcurrentMap::new,函数的实现里面创建了ConcurrentMap集合
  • CH_CONCURRENT_ID:具备并发的特性、无序特性、强转特性的Stream标识

toCollection

public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
     
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> {
      r1.addAll(r2); return r1; },
                                   CH_ID);
    }

返回一个Collector,它按遇到顺序将输入元素累积到一个新的 Collection。Collection由提供的工厂创建。
这个toCollection方法主要目的是满足我们需要的数据结构,除了上面的set、List、Map、ConcurrentMap之外的数据结构作为容器

  • collectionFactory根据参数进行预订是哪种数据结构,如TreeMap:new;

三、Collector的第三个泛型返回值不是集合类型的静态方法

joining()

返回一个Collector,它将输入元素以指定的分隔符分隔,并使用指定的前缀和*后缀按遇到顺序连接起来。

 public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
     
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
    }

具体实现里,首先映入眼帘的就是StringJoiner这个字符串类,当然我这里就不分析它了,这里就是对joining()方法进行整体上的分析,想要了解StringJoiner这个JDK1.8新增加的String类,可以去看一下。

  • () -> new StringJoiner(delimiter, prefix, suffix):大家可以认为是用的容器
  • StringJoiner::add:进行add添加操作,具体内幕操作,看下StringJoiner的add方法细节。
  • StringJoiner::merge:进行merge集合合并操作,具体内幕操作,看下StringJoiner的merge方法细节。
  • StringJoiner::toString,:进行类型转换操作,最后需要返回Sting类型参数,具体内幕操作,看下StringJoiner的toString方法细节。
  • CH_NOID:对Stream流操作没有进行任何标记操作,Stream会按照最原始的方式进行Stream计算。

在这里再强调一下,本小节是带着大家学习函数式思维的,以及分析能力,而不是见什么都要的,当然 joining() 还有两个重载方法,都是最终调用了当前方法,所以就不去分析了。

counting()、reducing()一起分析

 public static <T> Collector<T, ?, Long>
    counting() {
     
        return reducing(0L, e -> 1L, Long::sum);
    }
    
 public static <T, U>
    Collector<T, ?, U> reducing(U identity,
                                Function<? super T, ? extends U> mapper,
                                BinaryOperator<U> op) {
     
        return new CollectorImpl<>(
                boxSupplier(identity),
                (a, t) -> {
      a[0] = op.apply(a[0], mapper.apply(t)); },
                (a, b) -> {
      a[0] = op.apply(a[0], b[0]); return a; },
                a -> a[0], CH_NOID);
    }

返回Collector接受类型为T的元素,该类型计算输入元素的数量。如果不存在元素,结果为0。
这个里面设计到reducing()函数。

分析reducing三个参数:

  • U identity=0L也就是Long identity=0L;
  • Function mapper=e -> 1L;也就是Function mapper=e -> 1L;
  • BinaryOperator op=Long::sum;也就是BinaryOperator op=Long::sum;

reducing具体实现的 CollectorImpl五个参数为

  • boxSupplier(identity):生成一个存放着identity的T[]数组,生成泛型数组,不能直接创建所以需要强转成Object[]超类数组
@SuppressWarnings("unchecked")
    private static <T> Supplier<T[]> boxSupplier(T identity) {
     
        return () -> (T[]) new Object[] {
      identity };
    }
  • (a, t) ->{a[0] = op.apply(a[0], mapper.apply(t)); };
    a参数为 boxSupplier(identity)生成的数组,t为每次需处理的参数,mapper.apply(t)调用e -> 1L,结果值为1L,这个时候op.apply(a[0], 1L); };而identity=0L放入的数组a[0]=0L,这个时候
    op.apply(0L, 1L); 然后就是调用op,也即是Long::sum,long值的sum计算,所以是0+1=1.之后更新数组第一个元素值a[0]。
(a, t) ->{
     a[0] = op.apply(a[0], mapper.apply(t));   };
相当于
Object[] a=new Object[0];
a[0]=a[0]+t;
  • (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; }:将a数组第一个元素和b数组的第一个元素进行相加,然后赋值给a[0],返回a数组,也就是数组的合并操作。
 (a, b) -> {
      a[0] = op.apply(a[0], b[0]); return a; }:
 等价于
 	Object[] a=new Object[0];
	a[0]=a[0]+t;
	Object[] b=new Object[0];
	b[0]=b[0]+t;
	a[0]=a[0]+b[0]
	reture a[0];
  • CH_NOID:没有任何Collter特性标记

这个时候 CollectorImpl已经完成了,可以进行外部使用。

reducing()还有一个重载的方法,这里就不进行分析了,最终的实现过程和上面reducing()基本一致。

总结

还有一些进行计算的函数式方法如summingInt()、summingLong()、averagingInt()等等,其实他们实现方式都是分步骤进行的,只是已经进行实现了而已,都是如下步骤:

  • 创建集合
  • 相应的逻辑放入第一步的集合
  • 将多个第二步的集合进行合并
  • 如果需要进行元素类型转换,进行类型转换

所以其他的计算方法就不在这里进行分析了,接下来的一小节即将对比较复杂的分组和分区进行深入分析,欢迎观看。

你可能感兴趣的:(StreamAPI)