Collector-and-Stream.reduce()
背景
当然,最简洁的方式是
String s = number.replace(“ “, “”).replace(“-“, “”);
但这里暂时忽略这种方案,只是为了指出该背景
一种较复杂的方案是使用 Stream,例如
1 | |
在这里,要将字符数组重新收集成 String 的合理做法应该只有使用:
1 | |
其余做法例如 mapToObj(String::valueOf).collect(joining) 会频繁创建 String;
而 reduce 严格来讲既不是可变规约也没有简洁多少
1 | |
实例化 CollectorImpl
这里只对
Collector做该话题下的简单介绍,详细知识见 javadocs
Collector 接口由四个抽象函数指定,这些函数协同工作以将条目累积到可变结果容器中,并可选择对结果执行最终转换。他们是:
- supplier() :创建一个新的结果容器
- accumulator() :将新数据元素合并到结果容器中
- combiner():将两个结果容器合并为一个( )
- finisher() :对容器执行可选的最终转换
Collector 接口在 JDK 中的实现类位于 Collectors.CollectorImpl
而实例化 CollectorImpl 有两种途径:
- 通过
Collectors类预定义的例如Collectors.toList()等静态工厂方法实例化 - 通过
Collector接口中暴露出的两种Collector.of方法实例化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public interface Collector<T, A, R> {
public static<T, R> Collector<T, R, R> of
(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics)
{...}
public static<T, A, R> Collector<T, A, R> of
(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics)
{...}
}
可以看到,Collector 接口只提供了两种重载用于实例化 CollectorImpl
并且这两种重载都必须传入 supplier 、accmulator 以及 combiner,前两个参数很好理解,毕竟 CollectorImpl 不好对此提供默认的实现
但是对于组合器 combiner,由于组合器只有在执行并发规约时会使用到,也就是说,对于上面场景下的收集器(即不考虑使用并发流的场景),提供一个 combiner 并没有实际意义
为什么必须提供 combiner
在该问题中,发现这种操作在 Scala 中被称为foldLeft。需要注意的是,Java 的库函数中并没有提供等效于 foldLeft 的实现。
在上面的回答中提到:
Finally, Java doesn’t providefoldLeftandfoldRightoperations because they imply a particular ordering of operations that is inherently sequential. This clashes with the design principle stated above of providing APIs that support sequential and parallel operation equally.
最后,Java 不提供foldLeftandfoldRight操作,因为它们暗示了一种特定的操作顺序,这种顺序本质上是顺序的。这与上述提供同样支持顺序和并行操作的 API 的设计原则相冲突。
虽然该说法有一定说服力,但还是继续搜索了为什么 Java 没有提供 foldLeft,试图继续理解所提到的设计原则。
但是结果却找到了JDK-8292845和JDK-8292845,但在这两个增强请求中,却没有对相关设计原则进行讨论,而是计划会在将来对此进行实现。
也许在不久的将来,就会有一种更合理的 folding operations 可以替换上方看似不合理的实现
reduce vs collect
先看一段代码:
1 | |
发现在 parallel stream 下 reduce() 的输出并不符合我们的预期,先查看 reduce() 的方法注释
1 | |
对这里的约定进行验证:
1 | |
发现我们的用例其实是符合 reduce() 方法在 javadocs 中的约定的,于是继续查看相关代码
1 | |
会发现这其实是因为 reduce() 只是 Reduction operations 导致的(而 StringBuilder 是可变对象),在该场景下应该使用 Mutable reduction(可变规约) ,也就是 collect()
实际上仔细查看代码会发现 reduce 和 collect 的累加器 accumulator 定义也并不一样
1 | |
总结
how-does-reduce-method-work-with-parallel-streams-in-java-8
The problem lies in you using Stream::reduce for mutable reduction.
You should instead use Stream::collect
java-8-streams-collect-vs-reduce
The reason is simply that:
- collect() can only work with mutable result objects.
- reduce() is designed to work with immutable result objects.
java-8-streams-collect-vs-reduce
reduce是一个“折叠”操作,它将二元运算符应用于流中的每个元素,其中运算符的第一个参数是前一个应用程序的返回值,第二个参数是当前流元素。
collect是一种聚合操作,其中创建“集合”并将每个元素“添加”到该集合中。然后将流中不同部分的集合添加到一起。