java8学习总结——Collectors类源代码分析

来源:互联网 发布:网络招标 编辑:程序博客网 时间:2024/06/05 10:08

概述

        在JDK8中,对Collectos类的定义:一个Collector接口的实现,并提供很多有用的方法。事实也是如此,通过Collectors提供的方法,我们可以完成大多数日常的集合运算操作。因此,Collectors类的所有方法返回都是Collector的实例。其实,Collectors使Collector更具体化。因为,Collector只是约束了一种操作运算的规范(提供容器,计算,归集,finisher得出最终结果),而Collectors则提供了各种各样的,具有具体功能的Collector。比如:toList(),、toMap、toSet()、joining()等等Collector,这些Collector都是有具体的功能的。

joining():将字符串元素拼接起来;

toSet():将操作的元素以Set集合形式输出;

toMap() :将操作元素以Map形式输出;

所以说,Collectors使Collector更加的具体化,简洁实用。


        第一组:toSet、toMap、toCollectioin、toList、toConcurrentMap

这组Collector都是输出集合对象,它们的实现方式也大同小异,但是需要对Collector的实现方式很熟悉才可以很轻松看懂下面的源代码。

源代码:

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);}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);}public static <T>    Collector<T, ?, Set<T>> toSet() {        return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,                                   (left, right) -> { left.addAll(right); return left; },                                   CH_UNORDERED_ID);}

从上面的代码中可以看出,toSet、toList、toCollection的实现方式都是通过创建ConllectorImpl来获得具体的Collector,创建的方式也很简单。首先创建一个集合容器;其次将元素放入集合容器中,最后进行归集操作conbiner,将多个结果集合合并为一个结果集合输出。由于输出类型与输入类型一致,也就没有finisher方法了。

       只有map集合稍微复杂些。虽然toMap和toConcurrentMap也是通过创建CollectorImpl对象来实现的,但是由于Map集合的特殊性,所以在实现上有些微的差别。

源代码:

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

       从传入的参数上来看,toMap不仅有suppier方法,同时还有keyMapper和valueMapper两个Function,还有mergeFunctionh。前两个方法是用来分别获得存入map对象的key和value的。其传入参数是待操作的元素(element),而两个函数分别根据element获得key和value值。mergeFunction是用来将key和value值融入map集合中的。看一下map集合的merge方法的实现,或许可以更清晰一些。

源代码:

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;}
这里的remappingFunction函数就是mergeFunction。从上面的源代码中可以看到,merge方法会先从map中根据当前key获取元素值oldValue,如果获取不到元素值oldValue,则直接将获得value值赋给newValue,然后放入集合中。如果能够获取到oldValue值,则将新的value和oldValue同时传递给remappingFunction函数,获得的返回值就是newVlaue。也就是map的新value值。将以上的操作封装成一个Biconsumer就得到了accumulator方法。


       第二组:groupingBy,partioningBy

源代码:



 Collectors创建Collector,将其具体化为某种行为。创建Collector需要五个参数,上面红框标注的分别是前四个参数supplier、accumulate、combiner、finisher。


       
classifer函数接收的是T类型返回的是K类型。K类型正好就是分组函数中的key的类型,然后根据key去获取容器,这里应用了computeIfAbsent方法。调用computeIfAbsent方法的是一个map对象,即m。computeIfAbsent的计算方式是:如果存在则返回,不存在则创建。也就是说,这个方法会为每一个唯一的key创建一个容器,并且将这个容器放入m的map中,这也就达到了根据key进行分组的目的。m的结构示意如下:

                                            Map<key  ,  Map<K  ,  A>>

接下在再第二步时,通过mapMerge方法获得merger,即combiner。mapMerge方法的源代码如下:

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;        };}
里面调用了merge方法,merge方法会先判断m1中是否存在当前key的value值,即oldValue,如果存在,则使用mergeFunction将oldValue和m2中的value值进行汇集,并将结果放入m1;如果不存在,直接将key和value值放入m1中。

注: 这里只是解释这个mapMerger的运算过程,实际上在这里是不会执行这些运算的,这里只是将这一系列的运算方式封装成了merger,即Colletor中的combiner。

第三步没有太多的可说的,就是一个强制类型转换获得mangleFactory,即Collector中的supplier。这里解释一下为什么可以进行强制类型转换。



在理解完上面的groupingBy的源代码之后,再来看partitioningBy就简单很多了。

源代码:


第一步是创建了一个partition对象,这个对象包含两个属性forTrue和forFalse,在这里也分别创建了两个集合分别对应两个属性,这也符合partitionBy的本意,即将集合切分成true和false两个结果集合,根据给定的条件。

第二步也是创建了一个partition对象,分别应用downstream的combiner方法将true的结果集合和false的结果集合分别合并。然后就得到了两个结果集合。

第一个红框内,通过传入的predicate,如果predicate返回结果为true,则向downstreamaccumulate传入forTrue集合,认知则传入forFalse集合,以达到分组的目的。最后finisher方法的获取也是通过partition对象来完成的,方式很简单,就不一一赘述。partition的源代码如下:

private static final class Partition<T>            extends AbstractMap<Boolean, T>            implements Map<Boolean, T> {        final T forTrue; //保存true类型的集合        final T forFalse; //保存false类型的集合        Partition(T forTrue, T forFalse) {            this.forTrue = forTrue;            this.forFalse = forFalse;        }        @Override        public Set<Map.Entry<Boolean, T>> entrySet() {            return new AbstractSet<Map.Entry<Boolean, T>>() {                @Override                public Iterator<Map.Entry<Boolean, T>> iterator() {                    Map.Entry<Boolean, T> falseEntry = new SimpleImmutableEntry<>(false, forFalse);                    Map.Entry<Boolean, T> trueEntry = new SimpleImmutableEntry<>(true, forTrue);                    return Arrays.asList(falseEntry, trueEntry).iterator();                }                @Override                public int size() {                    return 2;                }            };        }    }

        第三组:mapping,reducing

        这两个方法辅助的性质更强一些。mapping方法可以转换Collector的接收元素类型。比如:Collector<T,A,D>的接收元素类型是T,通过mapping方法可以转换成为Collector<U,A,D>,这里的接收元素类型是U。

        这是mapping方法的特点,所以mapping方法的参数是两个:第一个参数是类型转换方法Function<T,U>,接收一个T类型的参数,返回一个U类型的值; 第二个参数是Collector<T,A,D>的实例。

源代码:

public static <T, U, A, R>    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,                               Collector<? super U, A, R> downstream) {        BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();        return new CollectorImpl<>(downstream.supplier(),                                   (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),                                   downstream.combiner(), downstream.finisher(),                                   downstream.characteristics());}
从源代码中可以看到,Collector的在accumulate的接收参数之前,先应用了mapper的方法,然后将返回值输入给accumulate。以此来达到转换Collector接收类型的目的。

       至于reducing方法,这个方法是将BinaryOperator操作转换成Collector接口实例,然后应用到Collector接收的每个元素上。

源代码:

public static <T> Collector<T, ?, T>    reducing(T identity, BinaryOperator<T> op) {        return new CollectorImpl<>(                boxSupplier(identity),                (a, t) -> { a[0] = op.apply(a[0], t); },                (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },                a -> a[0],                CH_NOID);}
reducing接收的两个参数,第一个参数(identity),是一个默认值,当没有输入元素时,将会输出identity。第二个参数就是BinaryOperator操作。reducing方法更多的是被summingInt、summingDouble、summingLong等方法调用,用于计算数字的总和。