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

时间:2016-04-29 03:05:34

标签: java java-8 java-stream collectors

我们知道Java 8引入了新的Stream API,java.util.stream.Collector是定义如何聚合/收集数据流的接口。

但是,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);
}

后者更易于实施。将它设计为前者的考虑是什么?

3 个答案:

答案 0 :(得分:26)

实际上它最初的设计与您的建议类似。请参阅项目lambda存储库中的the early implementationmakeResult现在是supplier)。它是当前设计的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,因此在调用{{1}时没有其他间接功能},suppliercombiner:您只需直接调用原始收集器返回的函数。对于finisher来说,这一点更为重要:

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); } ,但使用了原始finishersupplieraccumulator。当为每个元素调用combiner时,减少间接可能非常重要。尝试使用您提出的设计重写accumulatormapping,您将看到问题所在。像collectingAndThenfiltering这样的新JDK-9收集器也受益于当前的设计。

答案 1 :(得分:18)

Composition is favored over inheritance.

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

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

答案 2 :(得分:0)

2相关原因

  • 功能成分通过组合器。 (注意你仍然可以做OO组合但是看下面的点)
  • 当分配目标是功能界面时,可以通过lambda表达式或方法引用在简洁的富有表现力的代码中构建业务逻辑。

    功能组合

    Collectors API为组合器的功能组合铺平了道路。构建小/最小的可重用功能,并将其中一些通常以有趣的方式组合到一个高级功能/功能中。

    简明扼要的表达代码

    下面我们使用函数指针(Employee :: getSalary)将mapper的功能从Employee对象填充到int。 summingInt填充了添加整数的逻辑,因此结合在一起我们在一行声明代码中写出了工资总和。

    //计算员工工资总额  int total = employees.stream()                    .collect(Collectors.summingInt(<强>雇员::的getSalary )));