reduce() 方法在 Java 8 中是如何工作的?
Posted
技术标签:
【中文标题】reduce() 方法在 Java 8 中是如何工作的?【英文标题】:How does the reduce() method work in Java 8? 【发布时间】:2019-09-22 00:29:28 【问题描述】:我试图了解reduce()
方法在java-8 中是如何工作的。
例如我有这个代码:
public class App
public static void main(String[] args)
String[] arr = "lorem", "ipsum", "sit", "amet";
List<String> strs = Arrays.asList(arr);
int ijk = strs.stream().reduce(0,
(a, b) ->
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
,
(a, b) ->
System.out.println("Combiner");
return a * b;
);
System.out.println(ijk);
输出是这样的:
Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17
它是这些字符串长度的总和。而且我看到没有访问组合器,所以它不会乘以数字,它只会添加数字。
但如果我将stream
替换为parallelStream
:
int ijk = strs.parallelStream().reduce(0,
(a, b) ->
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
,
(a, b) ->
System.out.println("Combiner");
return a * b;
);
System.out.println(ijk);
这是输出:
Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300
我看到累加器和组合器都被访问了,但只有乘法是返回的。那么总和会发生什么?
【问题讨论】:
combiner
仅用于并行流。还请仔细阅读reduce的文档,您不能在accumulator
中进行求和,在combiner
中进行乘法运算并期望得到有意义的结果
【参考方案1】:
您应该阅读reduce
的文档:
另外,combiner 函数必须与 accumulator 函数兼容;对于所有 u 和 t,必须满足以下条件:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
在您的情况下,您违反了该法律(在 accumulator
中进行 sum 并在 combiner
中进行 乘法),因此您看到的结果是一个操作实际上是未定义的,取决于底层源的 Spliterator 是如何实现的(不要那样做!)。
此外,combiner
仅为并行流调用。
当然,您的整个方法可以简化为:
Arrays.asList("lorem", "ipsum", "sit", "amet")
.stream()
.mapToInt(String::length)
.sum();
如果您这样做只是为了学习,正确的reduce
将是(获取sum
):
strs.parallelStream()
.reduce(0,
(a, b) ->
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
,
(a, b) ->
System.out.println("Combiner");
return a + b;
);
【讨论】:
【参考方案2】:关键概念:标识、累加器和组合器
Stream.reduce() 操作:让我们将操作的参与者元素分解为单独的块。这样,我们将更容易理解每个人所扮演的角色
Identity – 一个元素,它是归约操作的初始值,如果流为空,则为默认结果 itemAccumulator – 一个接受两个参数的函数:归约操作的部分结果和流的下一个元素 Combiner – 一个接受两个参数的函数:归约操作的部分结果和流的下一个元素 组合器 - 当归约并行化或累加器参数的类型与累加器实现的类型不匹配时,用于组合归约操作的部分结果的函数当流并行执行时,Java 运行时会将流拆分为多个子流。在这种情况下,我们需要使用一个函数将子流的结果合并为一个。这就是combiner的作用
案例 1:Combiner 与 parallelStream
一起使用,如您的示例所示
案例 2:具有不同类型参数的累加器示例
在这种情况下,我们有一个用户对象流,累加器参数的类型是整数和用户。但是,累加器的实现是整数的总和,所以编译器无法推断用户参数的类型。
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
编译错误
The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> )
我们可以通过使用组合器来解决这个问题:这是方法引用Integer::sum
或使用 lambda 表达式(a,b)->a+b
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);
简单地说,如果我们使用顺序流并且累加器参数的类型和它的实现类型匹配,我们就不需要使用组合器。
【讨论】:
【参考方案3】:使用java-stream 有 3 种减少方法。简而言之,Stream::reduce
从两个后续项(或一个标识值与第一个项)开始,并与它们执行操作以产生新的减少值。对于下一个项目,同样的情况会发生,并使用减少的值执行操作。
假设您有'a'
、'b'
、'c'
和'd'
的流。归约执行以下操作序列:
result = operationOn('a', 'b')
- operationOn
可能是任何东西(输入长度的总和......)
result = operationOn(result, 'c')
result = operationOn(result, 'd')
result is returned
方法有:
Optional<T> reduce(BinaryOperator<T> accumulator)
对元素进行缩减。从产生减少值的前两个项目开始,然后每个项目产生减少值。返回Optional<T>
,因为不能保证输入流不为空。
T reduce(T identity, BinaryOperator<T> accumulator)
与上面的方法相同,除了标识值作为第一项给出。 T
被返回,因为由于T identity
,始终保证至少有一项。
U reduce(U identity, BiFunction<U,? super T, U> accumulator, BinaryOperator<U> combiner)
和上面的方法一样,只是功能是合并的。 U
被返回,因为由于U identity
,总是至少有一项保证。
【讨论】:
将身份值称为“默认值”是一种误导,如果不是更糟的话。如果您需要默认值,请使用reduce(function).orElse(default)
。此外,对第三次重载的解释,“加上功能被组合”并没有真正的帮助。第三个版本的要点是,它允许归约到与 Stream 的元素类型不同的结果类型。与直接替代方案stream.map(…).reduce(id, function)
的区别在于,您可以使用优化函数将源元素 (T
) 与结果元素 (U
) 组合在一起(如果有的话)。
“initial”可能比“default”更好,对吧?我仍然提高我的英语。感谢您的留言:)
嗯,“身份价值”这个词本身已经是一个词,没有更好的替代词。 “初始”和“默认”都不够。身份值的契约在文档中进行了解释,但是,正如 this answer 中所解释的,它是一个术语,而不仅仅是为归约操作而发明的。【参考方案4】:
我假设您选择做加法和乘法只是为了看看究竟发生了什么。
正如您已经注意到的那样,正如已经提到的,组合器仅在并行流上调用。
简而言之,在并行流上,流的一部分(分别是底层的 Spliterator)被截断并由不同的线程处理。在处理了几个部分后,它们的结果会被组合器组合起来。
在您的情况下,这四个元素都由不同的线程处理,然后按元素进行组合。这就是为什么你看不到任何加法(除了0 +
),而只有乘法。
但是,为了获得有意义的结果,您应该从*
切换到+
,而是进行更有意义的输出。
【讨论】:
以上是关于reduce() 方法在 Java 8 中是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在java 8中转换类型的reduce方法需要一个组合器