【问题标题】:Why is the Java 8 'Collector' class designed in this way?为什么 Java 8 的“收集器”类是这样设计的?
【发布时间】:2016-08-24 01:21:10
【问题描述】:

我们知道 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);
}

后一个更容易实现。把它设计成前者是出于什么考虑?

【问题讨论】:

  • 都是关于OOP和结构一致的,只是简单的猜测。
  • 注意,你可以实现一个抽象基类来适应第二种模式。
  • @Thilo 我认为第一种和第二种模式都可以在各个方向上进行调整。我只是觉得第二个更直观。
  • much easier to implement 不是真的。不是让你的函数在 supply 中,而是返回一个调用该函数的 lambda,仅此而已。

标签: java java-8 java-stream collectors


【解决方案1】:

实际上,它最初的设计与您的建议相似。请参阅项目 lambda 存储库中的 the early implementationmakeResult 现在是 supplier)。现在的设计是后来的updated。我相信,这种更新的基本原理是简化收集器组合器。我没有找到关于这个主题的任何具体讨论,但我的猜测得到了mappingcollector 出现在同一个变更集中的事实的支持。考虑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,您会发现问题所在。 filteringflatMapping 等新的 JDK-9 收集器也受益于当前的设计。

【讨论】:

    【解决方案2】:

    Composition is favored over inheritance.

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

    在第二种模式中,收集器实现必须自己实现所有功能。各种变体都需要覆盖父实现。没有太多可重用的空间,如果两个收集器对一个步骤有相似的逻辑,例如累加,就会出现代码重复。

    【讨论】:

    • 感谢您的回复。但是对于我的版本,通过这些 lambda 函数实现 Collector.of(....) 的辅助包装版本也很容易。
    【解决方案3】:

    2个相关原因

    • 功能组合通过组合器。 (请注意,您仍然可以进行 OO 组合,但请看以下几点)
    • 当分配目标是功能接口时,可以通过 lambda 表达式或方法引用在简洁的表达代码中构建业务逻辑。

      功能组成

      收集器 API 为通过组合器进行功能组合铺平了道路。即构建小型/最小的可重用功能,并经常以有趣的方式将其中一些组合成高级特性/功能。

      简洁的表达代码

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-23
      • 2022-11-13
      • 2012-05-05
      • 1970-01-01
      • 1970-01-01
      • 2010-11-10
      • 2014-06-24
      • 1970-01-01
      相关资源
      最近更新 更多