Java8 归约 reduce
Java8 归约 reduce
本节将看到如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总的卡路里”或“菜单中卡路里最高的菜时哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将一个操作看成一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
元素求和
在我们研究如何使用reduce方法之前,先来看看如何使用for-each循环来对数字列表中的元素求和:
Int sum = 0;
for (int x : numbers)
Sum += x;
numbers中的每一个元素都用加法运算符反复迭代来得到结果。通过反复使用加法,你把一个数字列表归约成了一个数字。这段代码中有2个参数:
1.总和变量的初始值,在这里是0;
2.将列表中所有元素结合在一起的操作,在这里是+。
要是还能把所有的数字相乘,而不必去复制粘贴这段代码,岂不是很好?这正是reduce操作的用武之地,它对这种重复应用的模式做了抽象。你可以像下面这样对流中所有的元素求和:
Int sum = numbers.stream().reduce(0, (a,b) -> a+b);
reduce 接受2个参数:
- 一个初始值,这里是0:
- 一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是lambda(a, b) -> a+b。
你也很容易把所有的元素相乘,只需要将另一个Lambda:(a, b) -> a*b
传递给reduce操作就可以了:
int product = numbers.stream().reduce(1, (a,b) -> a*b);
下图展示了reduce操作时如何作用于一个流的:Lambda反复结合每个元素,直到流被归约成一个值。
让我们深入研究一下reduce操作时如何对一个数字流求和的。首先,0作为Lambda(a)的第一个参数,从流中获取4作为第二个参数(b)。0+4得到4,它成了新的累积值。然后再用累积值和流中下一个元素5调用Lambda,产生新的累积值9。接下来,再用累积值和下一个元素3调用Lambda,得到12。最后,用12和流中最后一个元素9调用Lambda,得到最终结果12。
你可以使用方法引用让着段代码更简洁。在java8中,Integer类现在有了一个静态的sum方法来对两个数求和,这恰好时我们想要的,用不着反复用Lambda写同一段代码了:
int sum = numbers.stream().reduce(Integer::sum);
无初始值
reduce 还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:
Optional<Integer> sum = numbers.stream().reduce((a,b) -> a+b);
为什么它返回一个Optional<Integer>呢?考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。现在看看reduce还能做什么。
如果对于有无初始值不太明白,可以查看源码:
这是有初始值的源码:
这是没有初始值的源码:
参考书籍:Java8实战