这个特定的例子对于 Java 8 流来说是非常有问题的。它们专为顺序不重要的操作而设计。
函数应用程序不是关联的。为了解释,让我们举一个更简单的例子,其中一个人想要取一个数字并将其除以一个数字列表:
static List<Double> dividers = Arrays.asList( 3.5, 7.0, 0.5, 19.0 );
public double divideByList( double a ) {
for ( Double d : dividers ) {
a /= d;
}
return a;
}
所以,你得到的是
a ÷ 3.5 ÷ 7.0 ÷ 0.5 ÷ 19.0
算术很简单 - 除法是左结合的,这意味着这等价于
a ÷ ( 3.5 × 7.0 × 0.5 × 19.0)
不是
a ÷ ( 3.5 ÷ 7.0 ÷ 0.5 ÷ 19.0 )
而不是
( a ÷ 3.5 ÷ 7.0 ) ÷ ( 0.5 ÷ 19.0 )
基于reduce/collectors 的流操作要求“reducing”操作是左关联的。这是因为他们想让操作被并行化,这样一些线程会做一些操作,然后可以合并结果。现在,如果您的 our 运算符是乘法而不是除法,这将不是问题,因为
a × 3.5 × 7.0 × 0.5 × 19.0
与
相同
(a × 3.5 × 7.0 ) × (0.5 × 19)
这意味着一个线程可以执行a × 3.5 × 7.0,另一个可以执行0.5 × 19.0 操作,然后您可以将结果相乘并得到与顺序计算相同的结果。但是对于除法,这是行不通的。
函数应用也是非关联的,就像除法一样。也就是说,如果您有函数f、g 和h,并运行顺序计算,您将得到:
result = val + f(val) + g(val + f(val)) + h(val + f(val) + g(val + f(val)))
现在,如果您有两个中间线程,一个应用f 和g,另一个应用h,并且您想要组合结果 - 没有办法将正确的值放入@首先是987654340@。
您可能很想尝试使用Stream.reduce 之类的方法,正如@Bohemian 所建议的那样。但是documentation 警告您不要这样做:
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
...
标识值必须是组合器函数的标识。这意味着对于所有 u,combiner(identity, u) 等于 u。此外,combiner 函数必须与 accumulator 函数兼容;对于所有 u 和 t,必须满足以下条件:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
对于像+ 这样的操作,标识为0。对于*,标识为1。因此,将val 用作identity 是违反文档的。而第二个条件就更成问题了。
虽然非并行流的当前实现不使用 combiner 部分,这使得这两个条件都不需要,这没有记录 strong>,未来的实现或不同的 JRE 实现可能会决定创建中间结果并使用 combiner 来加入它们,可能是为了提高性能或出于任何其他考虑。
因此,尽管有诱惑,但应该不使用Stream.reduce 来尝试模仿最初的顺序处理。
有一种方法可以做到这一点,实际上并不会破坏文档。它涉及保留一个保存结果的可变对象(它必须是一个对象,以便它在仍然可变的同时有效地最终确定),并使用Stream.forEachOrdered,它保证操作将按照它们在流中出现的顺序执行, 如果流是有序的。并且列表的流具有定义的顺序。即使您使用myList.stream().parallel(),这也有效。
public static double streamedCalculate(double val) {
class MutableDouble {
double currVal;
MutableDouble(double initVal) {
currVal = initVal;
}
}
final MutableDouble accumulator = new MutableDouble(val);
myList.stream().forEachOrdered((x) -> accumulator.currVal += x.apply(accumulator.currVal));
return accumulator.currVal;
}
就个人而言,我发现您的原始循环比这更具可读性,因此在这里使用流确实没有任何优势。
根据@Tagir Valeev 的评论,计划在未来的Java 版本中使用foldLeft 操作。发生这种情况时,它可能看起来更优雅。