首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么在java 8中转换类型的reduce方法需要一个组合器?

为什么在java 8中转换类型的reduce方法需要一个组合器?
EN

Stack Overflow用户
提问于 2014-06-19 21:45:12
回答 4查看 53.4K关注 0票数 181

我很难完全理解combiner在Streams reduce方法中所扮演的角色。

例如,以下代码无法编译:

代码语言:javascript
复制
int length = asList("str1", "str2").stream()
            .reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length());

编译错误显示:(参数不匹配;int不能转换为java.lang.String)

但是下面的代码可以编译:

代码语言:javascript
复制
int length = asList("str1", "str2").stream()  
    .reduce(0, (accumulatedInt, str ) -> accumulatedInt + str.length(), 
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2);

我知道组合器方法是在并行流中使用的-所以在我的示例中,它将两个中间累加整数相加在一起。

但我不明白为什么第一个示例不能在没有组合器的情况下编译,也不明白组合器是如何解决string到int的转换的,因为它只是将两个int相加在一起。

有人能说明这一点吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2014-06-19 22:24:46

您尝试使用的两个和三个参数版本的reduce不接受相同类型的accumulator

有两个参数reducedefined as

代码语言:javascript
复制
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。

三个参数reducedefined as

代码语言:javascript
复制
<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就可以了。

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

代码语言:javascript
复制
int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .reduce(0, (accumulatedInt, len) -> accumulatedInt + len);

在这里,流的类型与reduce的返回类型相匹配,因此您可以使用两个参数版本的reduce

当然,你根本不需要使用reduce

代码语言:javascript
复制
int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .sum();
票数 87
EN

Stack Overflow用户

发布于 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的两个参数版本:

代码语言:javascript
复制
T reduce(I, (T, T) -> T)

顺序实现很简单。identity值I与第0个流元素“累加”,以给出结果。该结果与第一流元素累加以给出另一结果,该另一结果又与第二流元素累加,依此类推。最后一个元素累加后,返回最终结果。

并行实现首先将流分割成多个段。每个段都是由它自己的线程以我上面描述的顺序方式处理的。现在,如果我们有N个线程,我们就有N个中间结果。这些需要减少到一个结果。因为每个中间结果都是T类型的,并且我们有几个,所以我们可以使用相同的累加器函数来将这N个中间结果减少到单个结果。

现在,让我们考虑一个假设的两参数约简操作,该操作将Stream<T>减少为U。在其他语言中,这称为"fold"或“折叠-左”操作,所以我在这里将其称为“折叠-左”操作。注意,这在Java中是不存在的。

代码语言:javascript
复制
U foldLeft(I, (U, T) -> U)

(请注意,标识值I的类型为U。)

foldLeft的顺序版本就像reduce的顺序版本一样,只是中间值是U类型而不是T类型,但在其他方面是相同的。(假设的foldRight操作类似,只是操作是从右到左而不是从左到右执行的。)

现在考虑foldLeft的并行版本。让我们首先将流拆分成多个段。然后,我们可以让N个线程中的每个线程将其段中的T值减少为类型U的N个中间值。现在怎么办?我们如何从U类型的N个值到U类型的单个结果?

缺少的是另一个函数,将多个类型为U的中间结果组合成一个类型为U的结果。如果我们有一个将两个U值组合为一个的函数,就足以将任意数量的值减少到一个--就像上面的原始缩减一样。因此,给出不同类型结果的归约操作需要两个函数:

代码语言:javascript
复制
U reduce(I, (U, T) -> U, (U, U) -> U)

或者,使用Java语法:

代码语言:javascript
复制
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

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

最后,Java不提供foldLeftfoldRight操作,因为它们暗示了固有的顺序操作的特定顺序。这与上面提到的提供同等支持顺序和并行操作的API的设计原则相冲突。

票数 273
EN

Stack Overflow用户

发布于 2015-11-28 20:44:06

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

从字符串到字符串(顺序流)

假设有4个字符串:您的目标是将这些字符串连接成一个字符串。你基本上是从一个类型开始,然后以相同的类型结束。

您可以使用以下命令来实现此目的

代码语言:javascript
复制
String res = Arrays.asList("one", "two","three","four")
        .stream()
        .reduce("",
                (accumulatedStr, str) -> accumulatedStr + str);  //accumulator

这可以帮助你可视化正在发生的事情:

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

从字符串到整型(并行流)

假设有相同的4个字符串:你的新目标是对它们的长度求和,并且你想并行化你的流。

你需要的是这样的东西:

代码语言:javascript
复制
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结果中的规则,需要定义一个组合器。

从字符串到整型(顺序流)

如果你不想并行化你的流呢?好吧,无论如何都需要提供一个组合器,但它永远不会被调用,因为不会产生部分结果。

票数 148
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24308146

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档