StreamAPI源码分析之二(Collectors工厂类内部设计分析篇)

前言

前一小结对Collector接口进行了总结介绍,并没有对其实现进行展开,接下来对Collectors进行展开,Collections本身提供了关于Collectors的常见汇聚实现,Collectors其实就是一个工厂。collector由四个函数指定,这些函数一起工作,将条目累积到可变结果容器中,并可以选择对结果执行最终转换,将会在Collectors看到Collector的实习内幕。本节主要分析层次设计,不对细节方法进行深入分析,Collectors工厂类将以两小节进行分析,当我们看清楚Collectors工厂类内部设计的时候,可能对于源码的理解会更好。

1、Collectors介绍

  • Collector的实现,实现各种有用的reduction汇聚操作,例如将元素累积到集合中,根据各种条件汇总元素等。
  • 以下是使用预定义收集器执行*常见可变缩减任务的示例:
 		// 将姓名累加到列表中
 *     List<String> list = people.stream()
 * 								.map(Person::getName)
 * 								.collect(Collectors.toList());
 *
 *     // 把名字累加成树集
 *     Set<String> set1 =people.stream()
 * 						.map(Person::getName)
 * 						.collect(Collectors.toCollection(TreeSet::new));
 *
 *     // 将元素转换为字符串并将它们连接起来,用逗号分隔
 *     String joined = things.stream()
 *                           .map(Object::toString)
 *                           .collect(Collectors.joining(", "));
 *
 *     // 计算职工工资总额
 *     int total = employees.stream()
 *                          .collect(Collectors.summingInt(Employee::getSalary)));
 *
 *     // 按部门分组员工
 *     Map<Department, List<Employee>> byDept
 *         = employees.stream()
 *                    .collect(Collectors.groupingBy(Employee::getDepartment));
 *
 *     // 按部门计算工资总额
 *     Map<Department, Integer> totalByDept
 *         = employees.stream()
 *                    .collect(Collectors.groupingBy(Employee::getDepartment,
 *                    Collectors.summingInt(Employee::getSalary)));
 *
 *     //把学生分为及格和不及格
 *     Map<Boolean, List<Student>> passingFailing =
 *         students.stream()
 *              .collect(Collectors.partitioningBy(s -> s.getGrade()>=PASS_THRESHOLD));
 *

以上是对Collectors简单介绍,定义加示例方式;

2、Collectors类结构分析

①Collectors构造方法

抑制默认构造函数,确保不可实例化。

private Collectors() {
      }

② Collectors变量

前面已经有讲过在SztreamAPI源码分析之一对Characteristics的特性进行了分析,这里就做太多讲解了。

	static final Set<Collector.Characteristics> CH_CONCURRENT_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
                                                     Collector.Characteristics.UNORDERED,
                                    			Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_CONCURRENT_NOID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
                                                     Collector.Characteristics.UNORDERED));
    static final Set<Collector.Characteristics> CH_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_UNORDERED_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,
                                                     Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();

首先可以看出引用集合变量使用static +final修饰,static就不多介绍了在这里,因为这将是一个工厂类,对外将会是static方法提供,所以需要静态变量,final修饰符在这这里就是把初始化出来的变量不在被重新引用其他的指针,默认protected,本包下java.util.stream,所以注定了内部使用的集合容器,在加上Collections.unmodifiableSet生成不可变集合,保证线程安全。

  • CH_CONCURRENT_ID:该集合具备并发、无序、集合中元素不用强转标记特性;
  • CH_CONCURRENT_NOID:该集合具备并发、无序、集合中元素需要考虑元素前后转换不一致问题;
  • CH_ID:该集合具备集合中元素不用强转标记特性;
  • CH_UNORDERED_ID:该集合具备无序、集合中元素不用强转标记特性;
  • CH_NOID:该集合是一个空集合;

这些变量集合将在接下来的Collectors工厂类发挥极具重要的作用。

③ Collectors内部类

CollectorImpl

CollectorImpl是Collector真正的实现类,但是可能好多人肯定会有一个疑问,为什么不直接由Collectors实现那?这样子是不是多此一举那?
这里要从Java的特性开始讲起,Java有三大特性:继承、多态、封装。在我们普通的业务中,可能继承就是为了快速完成任务,上线交工,但是JDK的开发工作可不是一照这个流程进行的,而是想着1.8版本之后,这个类如何在伸缩性、拓展性、功能增强行考虑,当然还有性能。
那这个时候一个补全Java单继承短板的设计出现了,那就是嵌套类,即被定义在另一个类的内部的类。嵌套类存在的目的只是为了它的外围类提供服务。今天只有一个CollectorImpl内部类,可能下个版本就会有另一个内部提供另一种功能服务,而且与原有的功能不冲突,增加新内部类以及新方法,很好的避开了维护成本。这里设计成静态内部类是因为这是一个Collectors工厂类,方法都是静态方法,需要静态的变量,而这个时候,其实静态内部类只能当静态变量使用(可能有些描述不合适,可以这样先理解吧,如果有大佬知道,望指教下,但是确实是需要静态内部类)

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
     
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Function<A,R> finisher,
                      Set<Characteristics> characteristics) {
     
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
     
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }

        @Override
        public BiConsumer<A, T> accumulator() {
     
            return accumulator;
        }

        @Override
        public Supplier<A> supplier() {
     
            return supplier;
        }

        @Override
        public BinaryOperator<A> combiner() {
     
            return combiner;
        }

        @Override
        public Function<A, R> finisher() {
     
            return finisher;
        }

        @Override
        public Set<Characteristics> characteristics() {
     
            return characteristics;
        }
    }

CollectorImpl的五个变量

  • supplier:生产者,提供容器的接口
  • accumulator:累加器,进行计算的接口
  • combiner:合并器,将多线程生成的集合进行合并成一个集合的接口
  • finisher:完成器,执行从中间累积类型A到最终结果类型 R的最终转换
  • characteristics:显示此收集器的特性

两个四参五参的构造函数不进行解释了。之后就是Collector的五个方法进行了实现,基本都是返回工厂类的实现方式,放入是什么实现,就使用什么实现,也不做过多讲解了,没啥难度的。

Partition

一个私有静态常量内部类,首先这个类私有化就已经确定是为这个Collectors工厂类服务了,就是因为工厂类所以需要静态类来提供,常量类是因为需要保证这个类生成以后是一份,也而且这个类需要的情况比较单一,提供的服务比较单一,就是为Collectors工厂类一个分组方法提供服务;之后我们也看到 extends AbstractMap抽象Map,又实现了implements Map(这里多少一句,我个人觉得这里重复了,因为 AbstractMap已经实现了implements Map)这里就没有必要在明着实现implements Map

 private static final class Partition<T>
            extends AbstractMap<Boolean, T>
            implements Map<Boolean, T> {
     
        final T forTrue;
        final T forFalse;

        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;
                }
            };
        }
    }
  • 两个变量:

    • forTrue:被分到true区域的变量;
    • forFalse:被分到false区域的变量;
  • 构造方法:双参

  • 重写AbstractMap的 public abstract Set> entrySet();抽象方法

解读Partition的entrySet() 方法

首先方法中对AbstractSet抽象类的父类AbstractCollection的迭代器方法进行实现,

  • public abstract Iterator 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();

首先是生成两个新的Entry,然后进行合并并且迭代

之后对AbstractCollection的size()方法进行实现,原本AbstractSet集合的大小本来就是true集合和false集合,所以这里是定值2;

你可能感兴趣的:(StreamAPI)