为什么Java 8“Collector”类以这种方式设计?

2022-09-01 06:53:19

我们知道Java 8引入了一个新的Stream API,它是定义如何聚合/收集数据流的接口。java.util.stream.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);
}

后者更容易实现。将其设计为前者的考虑因素是什么?


答案 1

实际上,它最初的设计与您提出的类似。请参阅项目 lambda 存储库中的早期实现(现在是 )。它后来更新为当前设计。我相信,这种更新的基本原理是简化收集器组合器。我没有找到关于这个主题的任何具体讨论,但我的猜测得到了收集器出现在同一个变更集中的事实的支持。考虑以下各项的实现:makeResultsuppliermappingCollectors.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());
}

此实现只需要重新定义函数,保留 和 原样,因此在调用 时没有额外的间接寻址,或者 :您只需直接调用原始收集器返回的函数即可。更重要的是:accumulatorsuppliercombinerfinishersuppliercombinerfinishercollectingAndThen

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

这里只是更改,但原始 ,并且被使用。正如每个元素所要求的那样,减少间接寻址可能非常重要。尝试重写并使用您提出的设计,您将看到问题。新的JDK-9收集器喜欢并从当前的设计中受益。finishersupplieraccumulatorcombineraccumulatormappingcollectingAndThenfilteringflatMapping


答案 2

组合比继承更受青睐。

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

在第二种模式中,Collector 实现必须单独实现所有函数。所有类型的变体都需要覆盖父实现。没有太多的重用范围,如果两个收集器对一个步骤(例如累积)具有相似的逻辑,则加上代码重复。


推荐