现将看到如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。
此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。
这样的查询可以被归类为归约操作(将流归约成一个值)。
用函数式编程语言的术语来说,这称为折叠( fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。

1、元素求和

在我们研究如何使用reduce方法之前,先来看看如何使用for -each循环来对数字列表中的
元素求和
image.png
numbers中的每个元素都用加法运算符反复迭代来得到结果。
通过反复使用加法,你把一个数字列表归约成了一个数字。
这段代码中有两个参数
1)总和变量的初始值,在这里是0;
2)将列表中所有元素结合在一起的操作,在这里是+。
要是还能把所有的数字相乘,而不必去复制粘贴这段代码,岂不是很好?
这正是reduce操作的用武之地,它对这种重复应用的模式做了抽象。
可以像下面这样对流中所有的元素求和
int sum = numbers .stream() . reduce(0,(a, b) -> a+ b);
reduce接受两个参数: .
1)一个初始值,这里是0;
2)一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是lambda (a,b)->a+b。
你也很容易把所有的元素相乘,只需要将另一个Lambda (a, b) -> ab传递给reduce操作就可以了
int product = numbers . stream() .reduce(1,(a, b) -> a
b) ;
下图展示了reduce操作是如何作用于一个流的,Lambda反复结合每个元素,直到流被归约成一个值。
image.png
深入研究一下reduce操作是如何对一个数字流求和的。
首先,0作为Lambda (a)的第一个参数,从流中获得4作为第二个参数(b)。
0 + 4得到4,它成了新的累积值。
然后再用累积值和流中下一个元素5调用Lambda,产生新的累积值9。
接下来,再用累积值和下一个元素3调用Lambda,得到12。
最后,用12和流中最后一个元素9调用Lambda,得到最终结果21。
你可以使用方法引用让这段代码更简洁。
在Java 8中,Integer类现在有了一个静态的sum方法来对两个数求和,不用反复用Lambda写同一段代码了:
int sum = numbers .stream() . reduce(0,Integer: :sum) ;
无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个optional对象:
Optional sum = numbers. stream() . reduce((a,b) -> (a + b)) ;
为什么它返回一个optional 呢?
考虑流中没有任何元素的情况。
reduce操作无法返回其和,因为它没有初始值。
这就是为什么结果被包裹在一个optional对象里,以表明和可能不存在。

2、最大值和最小值

只要用归约就可以计算最大值和最小值了。
让我们来看看如何利用刚刚学到的reduce来计算流中最大或最小的元素。
正如你前面看到的,reduce接受两个参数
1)一个初始值
2)一个Lambda来把两个流元素结合起来并产生一个新值。
Lmbda是一步步用加法运算符应用到流中每个元素上的。
因此,你需要一个给定两个元素能够返回最大值的Lambda。
reduce操作会考虑新值和流中下一个元素,并产生一个新的最大值,直到整个流消耗完!你可以像下面这样使用reduce来计算流中的最大值。
Optional max = numbers . stream() . reduce (Integer: :max) ;
image.png
image.png
image.png
image.png
image.png