5.4 归约
“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为 归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为 折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
5.4.1 元素求和(reduce)
有初始值
T reduce(T identity, BinaryOperator<T> accumulator);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);
reduce接受两个参数:一个初始值,这里是0; 一个
BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a +b, Lambda反复结合每个元素,直到流被归约成一个值。
无初始值
Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
为什么它返回一个Optional呢?
考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。
5.4.2 最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
Optional<Integer> min = numbers.stream().reduce((x, y) -> x < y ? x : y);
5.4.3 统计个数
int count = menu.stream().map(d -> 1).reduce(0, (a, b) -> a + b);
long count = menu.stream().count();
归约方法的优势与并行化
reduce的好处在于,这里的迭代被内部迭代抽象掉了,这让内部实现得以选择并行执行reduce操作。外部迭代式求和例子要更新共享变量sum,这不是那么容易并行化的。如果你加入了同步,很可能会发现线程竞争抵消了并行本应带来的性能提升! 可变的累加器模式对于并行化来说是死路一条 !!!
这种计算的并行化需要另一种办法: 将输入分块,分块求和,最后再合并起来
流操作:无状态和有状态
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态。但诸如reduce、sum、max等操作需要内部状态来累积结果。在上面的情况下,内部状态很小。在我们的例子里就是一个int或double。不管流中有多少元素要处理,内部状态都是有界的。相反,诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流,但有一个关键的区别。 从流中排序和删除重复项时都需要知道先前的历史。这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题(把质数流倒序会做什么呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。
-----------------------------------------------------------------------------读书笔记摘自 书名:Java 8实战 作者:[英] Raoul-Gabriel Urma [意] Mario Fusco [英] Alan M