Eran的回答描述了双 arg 和三 arg 版本之间的差异,因为前者简化为 ,而后者简化为 。但是,它实际上并没有解释在简化为时需要额外的组合器功能。reduce
Stream<T>
T
Stream<T>
U
Stream<T>
U
Streams API 的设计原则之一是,API 不应该在顺序流和并行流之间有所不同,或者换句话说,特定的 API 不应该阻止流按顺序或并行正确运行。如果您的 lambda 具有正确的属性(关联、非干扰等),则按顺序或并行运行的流应提供相同的结果。
让我们首先考虑一下归约的双参数版本:
T reduce(I, (T, T) -> T)
顺序实现非常简单。标识值与第 0 个流元素“累加”,以提供结果。此结果与第一个流元素一起累积,以提供另一个结果,而该结果又与第二个流元素一起累积,依此类推。累积最后一个元素后,将返回最终结果。I
并行实现首先将流拆分为段。每个段都由其自己的线程以我上面描述的顺序方式处理。现在,如果我们有 N 个线程,我们就有 N 个中间结果。这些需要减少到一个结果。由于每个中间结果都是 T 类型,并且我们有多个,因此我们可以使用相同的累加器函数将这些 N 个中间结果减少到单个结果。
现在,让我们考虑一个假设的双 arg 约简操作,该运算简化为 。在其他语言中,这被称为“折叠”或“向左折叠”操作,这就是我在这里所说的。请注意,这在Java中不存在。Stream<T>
U
U foldLeft(I, (U, T) -> U)
(请注意,标识值为 U 类型。I
的顺序版本与 的顺序版本类似,只是中间值的类型为 U 类型而不是类型 T。但除此之外是一样的。(假设的操作是相似的,只是操作将从右到左而不是从左到右执行。foldLeft
reduce
foldRight
现在考虑 并行版本 。让我们首先将流拆分为多个段。然后,我们可以让每个 N 个线程将其段中的 T 值减小为 U 类型的 N 个中间值。我们如何从 U 型的 N 个值减少到 U 型的单个结果?foldLeft
缺少的另一个函数将 U 类型的多个中间结果组合成 U 类型的单个结果。如果我们有一个函数将两个U值合并为一个,这足以将任意数量的值减少到一个 - 就像上面的原始简化一样。因此,给出不同类型结果的约简运算需要两个函数:
U reduce(I, (U, T) -> U, (U, U) -> U)
或者,使用 Java 语法:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
总之,要并行约简到不同的结果类型,我们需要两个函数:一个将 T 元素累积到中间 U 值,另一个将中间 U 值组合成单个 U 结果。如果我们不切换类型,则结果发现累加器函数与合路器函数相同。这就是为什么归约到同一类型只有累加器功能,而归约到不同的类型需要单独的累加器和合路器函数。
最后,Java不提供和操作,因为它们意味着本质上是顺序的操作的特定顺序。这与上面所述的设计原则相冲突,即提供平等支持顺序和并行操作的 API。foldLeft
foldRight