Pre
Java 8 - Stream流骚操作解读见到过的终端操作都是返回一个 boolean ( allMatch 之类的)、 void( forEach )或 Optional 对象( findAny 等)。也见过了使用 collect 来将流中的所有元素组合成一个 List 。
我们这里将学会如何把一个流中的元素组合起来,使用 reduce 操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的是哪一个”。
什么是归约操作
此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个 Integer 。这样的查询可以被为归约操作 (将流规约成一个值)
用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是规约操作的结果。
元素求和 reduce
在我们研究如何使用 reduce 方法之前,先来看看如何使用 for-each 循环来对数字列表中的元素求和
代码语言:javascript复制int sum = 0;
for (int x : numbers) {
sum = x;
}
numbers 中的每个元素都用加法运算符反复迭代来得到结果。通过反复使用加法,你把一个数字列表归约成了一个数字。这段代码中有两个参数
- 总和变量的初始值,在这里是 0
- 将列表中所有元素结合在一起的操作,在这里是 。
要是还能把所有的数字相?,而不必去复制粘贴这段代码,岂不是很好?这正是 reduce 操作的用武之地,它对这种重复应用的模式做了抽象。你可以像下面这样对流中所有的元素求和:
代码语言:javascript复制int sum = numbers.stream().reduce(0, (a, b) -> a b);
reduce 接受两个参数:
- 一个初始值,这里是0;
- 一个 BinaryOperator 来将两个元素结合起来产生一个新值,这里我们用的是 lambda
(a, b) -> a b
。
你也很容易把所有的元素相?,只需要将另一个Lambda: (a, b) -> a * b 传递给 reduce操作就可以了
代码语言:javascript复制int product = numbers.stream().reduce(1, (a, b) -> a * b);
我们把demo完善一下
代码语言:javascript复制 public static void reduce(){
List<Integer> list = Arrays.asList(11,2,3);
int sum = list.stream().reduce(0, (a, b) ->a b);
System.out.println(sum);
int cal = list.stream().reduce(1,(a, b) ->a*b);
System.out.println(cal);
}
展示了 reduce 操作是如何作用于一个流的:Lambda反复结合每个元素,直到流被归约层一个新的值。
reduce如何运行的
我们深入研究一下 reduce 操作是如何对一个数字流求和的。
- 首先, 0 作为Lambda( a )的第一个参数,从流中获得 4 作为第二个参数( b ), 0 4 得到 4 ,它成了新的累积值。
- 然后再用累积值和流中下一个元素 5 调用Lambda,产生新的累积值 9 。
- 接下来,再用累积值和下一个元素 3调用Lambda,得到 12 。
- 最后,用 12 和流中最后一个元素 9 调用Lambda,得到最终结果 21
你可以使用方法引用让这段代码更简洁。在Java 8中, Integer 类现在有了一个静态的 sum方法来对两个数求和,这?好是我们想要的,用不着反复用Lambda写同一段代码了:
代码语言:javascript复制 public static void reduce(){
List<Integer> list = Arrays.asList(11,2,3);
int sum = list.stream().reduce(0,Integer::sum);
System.out.println(sum);
}
【无初始值】
reduce 还有一个重载的变体,它不接受初始值,但是会返回一个 Optional 对象:
代码语言:javascript复制Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a b));
为什么它返回一个 Optional 呢?考虑流中没有任何元素的情况。 reduce 操作无法返回其和,因为它没有初始值。这就是为什么结果被包?在一个 Optional 对象里,以表明和可能不存在。
最大值和最小值
原来,只要用归约就可以计算最大值和最小值了!让我们来看看如何利用??学到的 reduce来计算流中最大或最小的元素。
reduce 接受两个参数:
- 一个初始值
- 一个Lambda来把两个流元素结合起来并产生一个新值
Lambda是一步步用加法运算符应用到流中每个元素上的, 。因此,你需要一个给定两个元素能够返回最大值的Lambda。 reduce 操作会考虑新值和流中下一个元素,并产生一个新的最大值,直到整个流消耗完
可以像下面这样使用 reduce 来计算流中的最大值
代码语言:javascript复制Optional<Integer> max = numbers.stream().reduce(Integer::max);
要计算最小值,你需要把 Integer.min 传给 reduce 来替换 Integer.max :
代码语言:javascript复制Optional<Integer> min = numbers.stream().reduce(Integer::min);