Java 8 'Collector' class 为什么要这样设计?

Why is the Java 8 'Collector' class designed in this way?

我们知道Java8引入了一个新的StreamAPI和java.util.stream.Collector是定义如何aggregate/collect数据流的接口。

但是Collector接口是这样设计的:

public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
}

为什么不是这样设计的?

public interface Collector<T, A, R> {
    A supply();
    void accumulate(A accumulator, T value);
    A combine(A left, A right);
    R finish(A accumulator);
}

后一种更容易实现。设计成前者的考虑是什么?

Composition is favored over inheritance.

您问题中的第一个模式是一种模块配置。 Collector 接口的实现可以为 Supplier、Accumulator 等提供不同的实现。这意味着 可以从现有的 Supplier、Accumulator 等实现池中组合 Collector 实现。这也有助于重用,两个收集器可能使用相同的累加器实现。 Stream.collect() 使用提供的行为。

在第二种模式中,Collector 实现必须自己实现所有功能。各种变体都需要覆盖父实现。如果两个收集器的某个步骤具有相似的逻辑(例如,累加),则没有太多可重用的范围,加上代码重复。

实际上它最初的设计与您提出的类似。请参阅当前设计的 the early implementation in project lambda repository (makeResult is now supplier). It was later updated。我相信,这种更新的基本原理是简化收集器组合器。我没有找到关于此主题的任何具体讨论,但 mapping 收集器出现在同一变更集中这一事实支持了我的猜测。考虑 Collectors.mapping:

的实现
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());
}

此实现只需要重新定义 accumulator 函数,而 suppliercombinerfinisher 保持原样,因此在调用 suppliercombinerfinisher:直接调用原收集器返回的函数即可。 collectingAndThen:

更重要
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                            Function<R,RR> finisher) {
    // ... some characteristics transformations ...
    return new CollectorImpl<>(downstream.supplier(),
                               downstream.accumulator(),
                               downstream.combiner(),
                               downstream.finisher().andThen(finisher),
                               characteristics);
}

这里只改了finisher,沿用了原来的supplieraccumulatorcombiner。由于每个元素都会调用 accumulator ,因此减少间接访问可能非常重要。尝试用您建议的设计重写 mappingcollectingAndThen,您会发现问题所在。新的 JDK-9 收集器,如 filteringflatMapping 也受益于当前的设计。

2个相关原因

  • 通过组合器的功能组合。 (请注意,您仍然可以进行 OO 组合,但请查看以下内容)
  • 当赋值目标是功能接口.

    功能构成

    收集器 API 为通过组合器进行功能组合铺平了道路。构建 small/smallest 可重复使用的功能,并经常以有趣的方式将其中一些功能组合成高级 feature/function.

    简洁的表达代码

    下面我们使用函数指针(Employee::getSalary)来填充从Employee对象到int的映射器的功能。 summingInt 填充了添加整数的逻辑,因此我们将薪水总和写在一行声明性代码中。

    // 计算雇员的工资总和 整数总计 = employees.stream() .collect(Collectors.summingInt(Employee::getSalary)));