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
函数,而 supplier
、combiner
和 finisher
保持原样,因此在调用 supplier
、combiner
或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);
}
这里只改了finisher
,沿用了原来的supplier
、accumulator
、combiner
。由于每个元素都会调用 accumulator
,因此减少间接访问可能非常重要。尝试用您建议的设计重写 mapping
和 collectingAndThen
,您会发现问题所在。新的 JDK-9 收集器,如 filtering
和 flatMapping
也受益于当前的设计。
2个相关原因
- 通过组合器的功能组合。 (请注意,您仍然可以进行 OO 组合,但请查看以下内容)
当赋值目标是功能接口.
功能构成
收集器 API 为通过组合器进行功能组合铺平了道路。构建 small/smallest 可重复使用的功能,并经常以有趣的方式将其中一些功能组合成高级 feature/function.
简洁的表达代码
下面我们使用函数指针(Employee::getSalary)来填充从Employee对象到int的映射器的功能。 summingInt 填充了添加整数的逻辑,因此我们将薪水总和写在一行声明性代码中。
// 计算雇员的工资总和
整数总计 = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
我们知道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
函数,而 supplier
、combiner
和 finisher
保持原样,因此在调用 supplier
、combiner
或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);
}
这里只改了finisher
,沿用了原来的supplier
、accumulator
、combiner
。由于每个元素都会调用 accumulator
,因此减少间接访问可能非常重要。尝试用您建议的设计重写 mapping
和 collectingAndThen
,您会发现问题所在。新的 JDK-9 收集器,如 filtering
和 flatMapping
也受益于当前的设计。
2个相关原因
- 通过组合器的功能组合。 (请注意,您仍然可以进行 OO 组合,但请查看以下内容)
当赋值目标是功能接口.
功能构成
收集器 API 为通过组合器进行功能组合铺平了道路。构建 small/smallest 可重复使用的功能,并经常以有趣的方式将其中一些功能组合成高级 feature/function.
简洁的表达代码
下面我们使用函数指针(Employee::getSalary)来填充从Employee对象到int的映射器的功能。 summingInt 填充了添加整数的逻辑,因此我们将薪水总和写在一行声明性代码中。
// 计算雇员的工资总和 整数总计 = employees.stream() .collect(Collectors.summingInt(Employee::getSalary)));