为什么在Java 8中转换types的reduce方法需要组合器?

我很难完全理解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); 

我明白,合并器方法是并行stream – 所以在我的例子中,它是加在一起的两个中间积累的整数。

但我不明白为什么第一个例子不编译没有组合器或如何组合器是解决string转换为整数,因为它只是加在一起两个整数。

任何人都可以阐明这一点?

您试图使用的reduce的两个和三个参数版本不会接受accumulator的相同types。

这两个参数reduce 定义为 :

 T reduce(T identity, BinaryOperator<T> accumulator) 

在你的情况下,T是String,所以BinaryOperator<T>应该接受两个String参数并返回一个String。 但你传递给它一个int和一个string,这会导致你得到的编译错误 – argument mismatch; int cannot be converted to java.lang.String argument mismatch; int cannot be converted to java.lang.String 。 实际上,我认为传递0作为身份值在这里也是错误的,因为string是预期的(T)。

还要注意,这个reduce版本处理Tsstream并返回一个T,所以你不能用它来把Stringstream减less到int。

三个参数reduce 定义如下 :

 <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) 

在你的情况下,U是整数,T是string,所以这个方法将减lessstringstream为一个整数。

对于BiFunction<U,? super T,U> BiFunction<U,? super T,U> accumulator可以传递两种不同types的参数(U和?super T),在你的情况下是Integer和String。 另外,身份值U在你的情况下接受一个I​​nteger,所以传递0就可以了。

另一种方法来实现你想要的:

 int length = asList("str1", "str2").stream().mapToInt (s -> s.length()) .reduce(0, (accumulatedInt, len) -> accumulatedInt + len); 

这里stream的types匹配reduce的返回types,所以可以使用reduce的两个参数版本。

当然,你根本不用reduce

 int length = asList("str1", "str2").stream().mapToInt (s -> s.length()) .sum(); 

Eran的答案描述了两个arg和三个arg版本的reduce之间的区别,前者将Stream<T>减less到T而后者将Stream<T>减less到U 但是,当将Stream<T>简化为U时,实际上并没有解释需要额外的组合器function。

Streams API的devise原则之一是API不应该在顺序stream和并行stream之间不同,或者换句话说,特定的API不应该阻止顺序地或并行地正确运行stream。 如果您的lambda具有正确的属性(关联性,非干扰性等),则顺序运行或并行运行的stream应该得到相同的结果。

我们先来考虑两个缩减的版本:

 T reduce(I, (T, T) -> T) 

顺序执行很简单。 标识值I与第零个stream元素“累积”以给出结果。 这个结果与第一个stream元素一起累积,以得到另一个结果,而这个结果又与第二个stream元素累积,依此类推。 最后一个元素累积后,返回最终结果。

并行实现通过将stream拆分成段来开始。 每段都以自己的线程按上述的顺序处理。 现在,如果我们有N个线程,我们有N个中间结果。 这些需要减less到一个结果。 由于每个中间结果都是T型的,我们有几个,所以我们可以使用相同的累加器函数将这N个中间结果减less到单个结果。

现在让我们考虑一个假设的两个arg简化操作,将Stream<T>简化为U 在其他语言中,这被称为“折叠”或“折叠”操作,所以这就是我在这里所说的。 注意这在Java中不存在。

 U foldLeft(I, (U, T) -> U) 

(请注意,标识值I是U型的)

foldLeft的顺序版本就像foldLeft的顺序版本,只不过中间值的types是U而不是typesT.但是它是相同的。 (假设foldRight操作是相似的,除了操作将从右到左而不是从左到右执行。)

现在考虑foldLeft的并行版本。 我们首先将stream分成几部分。 然后,我们可以让N个线程中的每一个线程将其段中的T值减小到N个Utypes的中间值。现在呢? 我们如何从U型的N个值得到U型的单个结果?

缺less的是 U型的多个中间结果合并成一个U型的单个结果的另一个函数。如果我们有一个将两个U值合并成一个的函数,就足以将任意数量的值减less到一个 – 就像上面原来的还原。 因此,给出不同types结果的简化操作需要两个function:

 U reduce(I, (U, T) -> U, (U, U) -> U) 

或者,使用Java语法:

 <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) 

总之,为了对不同的结果types进行并行缩减,我们需要两个函数:一个将T元素累加到中间U值,另一个中间U值组合成单个U结果。 如果我们不切换types,那么累加器function与组合器function相同。 这就是为什么减less到相同types只有累加器function,减less到不同types需要单独的累加器和组合器function。

最后,Java不提供foldLeftfoldRight操作,因为它们意味着特定的操作顺序,这本身就是顺序的。 这与上面提到的提供支持顺序和并行操作的API的devise原则相冲突。

因为我喜欢涂鸦和箭头来澄清概念…让我们开始吧!

从string到string(顺序stream)

假设有4个string:你的目标是把这样的string连接成一个。 你基本上从一个types开始,并完成相同的types。

你可以做到这一点

 String res = Arrays.asList("one", "two","three","four") .stream() .reduce("", (accumulatedStr, str) -> accumulatedStr + str); //accumulator 

这可以帮助你直观地看到发生了什么:

在这里输入图像说明

累加器函数逐步将(红色)stream中的元素转换为最终减less的(绿色)值。 累加器函数只是将一个String对象转换成另一个String

从string到int(并行stream)

假设有相同的4个string:你的新目标是总结他们的长度,并且你想并行化你的stream。

你需要的是这样的:

 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数据。 作为平行stream,它被分割成两个(红色)部分,每个部分都是相互独立的,并产生同样多的部分(橙色)结果。 需要定义一个组合器来提供一个规则来将部分int结果合并到最终的(绿色) int

从string到int(顺序stream)

如果你不想并行化你的stream呢? 那么,无论如何都需要提供一个组合器,但由于不会产生部分结果,所以它将永远不会被调用。

没有减less版本,因为它不能并行执行(不知道为什么这是一个要求),没有合并器采取两种不同的types。 累加器必须关联的事实使得这个接口几乎没有用处,因为:

 list.stream().reduce(identity, accumulator, combiner); 

产生与以下相同的结果:

 list.stream().map(i -> accumulator(identity, i)) .reduce(identity, combiner);