我很难完全理解combiner
在Streams reduce
方法中所扮演的角色。
例如,以下代码无法编译:
int length = asList("str1", "str2").stream()
.reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length());
编译错误显示:(参数不匹配;int不能转换为java.lang.String)
但是下面的代码可以编译:
int length = asList("str1", "str2").stream()
.reduce(0, (accumulatedInt, str ) -> accumulatedInt + str.length(),
(accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2);
我知道组合器方法是在并行流中使用的-所以在我的示例中,它将两个中间累加整数相加在一起。
但我不明白为什么第一个示例不能在没有组合器的情况下编译,也不明白组合器是如何解决string到int的转换的,因为它只是将两个int相加在一起。
有人能说明这一点吗?
发布于 2014-06-19 22:24:46
您尝试使用的两个和三个参数版本的reduce
不接受相同类型的accumulator
。
有两个参数reduce
是defined as:
T reduce(T identity,
BinaryOperator<T> accumulator)
在本例中,T是字符串,所以BinaryOperator<T>
应该接受两个字符串参数并返回一个字符串。但是您将一个int和一个字符串传递给它,这将导致编译错误- argument mismatch; int cannot be converted to java.lang.String
。实际上,我认为在这里传递0作为标识值也是错误的,因为需要一个字符串(T)。
还要注意,这个版本的reduce处理一个Ts流并返回一个T,因此您不能使用它将字符串流缩减为int。
三个参数reduce
为defined as:
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
在本例中,U是Integer,T是String,因此此方法会将字符串流减少为Integer。
对于BiFunction<U,? super T,U>
累加器,您可以传递两种不同类型的参数(U和?super T),在您的例子中是Integer和String。此外,在本例中,标识值U接受一个Integer,因此传递它0就可以了。
另一种方法来实现你想要的:
int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
.reduce(0, (accumulatedInt, len) -> accumulatedInt + len);
在这里,流的类型与reduce
的返回类型相匹配,因此您可以使用两个参数版本的reduce
。
当然,你根本不需要使用reduce
:
int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
.sum();
发布于 2014-06-20 05:20:26
Eran's answer描述了reduce
的两个参数版本和三个参数版本之间的差异,因为前者将Stream<T>
简化为T
,而后者将Stream<T>
简化为U
。然而,它实际上并没有解释为什么在将Stream<T>
简化为U
时需要额外的组合器功能。
streams API的设计原则之一是,API不应该在顺序流和并行流之间有所不同,或者换句话说,特定的API不应该阻止流正确地运行,无论是顺序还是并行。如果您的lambda具有正确的属性(关联、非干扰等)顺序或并行运行的流应该会产生相同的结果。
让我们首先考虑一下reduction的两个参数版本:
T reduce(I, (T, T) -> T)
顺序实现很简单。identity值I
与第0个流元素“累加”,以给出结果。该结果与第一流元素累加以给出另一结果,该另一结果又与第二流元素累加,依此类推。最后一个元素累加后,返回最终结果。
并行实现首先将流分割成多个段。每个段都是由它自己的线程以我上面描述的顺序方式处理的。现在,如果我们有N个线程,我们就有N个中间结果。这些需要减少到一个结果。因为每个中间结果都是T类型的,并且我们有几个,所以我们可以使用相同的累加器函数来将这N个中间结果减少到单个结果。
现在,让我们考虑一个假设的两参数约简操作,该操作将Stream<T>
减少为U
。在其他语言中,这称为"fold"或“折叠-左”操作,所以我在这里将其称为“折叠-左”操作。注意,这在Java中是不存在的。
U foldLeft(I, (U, T) -> U)
(请注意,标识值I
的类型为U。)
foldLeft
的顺序版本就像reduce
的顺序版本一样,只是中间值是U类型而不是T类型,但在其他方面是相同的。(假设的foldRight
操作类似,只是操作是从右到左而不是从左到右执行的。)
现在考虑foldLeft
的并行版本。让我们首先将流拆分成多个段。然后,我们可以让N个线程中的每个线程将其段中的T值减少为类型U的N个中间值。现在怎么办?我们如何从U类型的N个值到U类型的单个结果?
缺少的是另一个函数,将多个类型为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不提供foldLeft
和foldRight
操作,因为它们暗示了固有的顺序操作的特定顺序。这与上面提到的提供同等支持顺序和并行操作的API的设计原则相冲突。
发布于 2015-11-28 20:44:06
因为我喜欢涂鸦和箭头来澄清概念。我们开始吧!
从字符串到字符串(顺序流)
假设有4个字符串:您的目标是将这些字符串连接成一个字符串。你基本上是从一个类型开始,然后以相同的类型结束。
您可以使用以下命令来实现此目的
String res = Arrays.asList("one", "two","three","four")
.stream()
.reduce("",
(accumulatedStr, str) -> accumulatedStr + str); //accumulator
这可以帮助你可视化正在发生的事情:
累加器函数一步一步地将(红色)流中的元素转换为最终的减去(绿色)值。累加器函数只是将一个String
对象转换为另一个String
。
从字符串到整型(并行流)
假设有相同的4个字符串:你的新目标是对它们的长度求和,并且你想并行化你的流。
你需要的是这样的东西:
int length = Arrays.asList("one", "two","three","four")
.parallelStream()
.reduce(0,
(accumulatedInt, str) -> accumulatedInt + str.length(), //accumulator
(accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2); //combiner
这是正在发生的事情的一个计划
在这里,累加器函数( BiFunction
)允许您将String
数据转换为int
数据。由于流是平行的,它被分成两个(红色)部分,每个部分都独立于其他部分进行阐述,并产生同样多的部分(橙色)结果。为了提供将部分int
结果合并到最终(绿色) int
结果中的规则,需要定义一个组合器。
从字符串到整型(顺序流)
如果你不想并行化你的流呢?好吧,无论如何都需要提供一个组合器,但它永远不会被调用,因为不会产生部分结果。
https://stackoverflow.com/questions/24308146
复制相似问题