reduce 方法允许我们在循环里面叠加计算值.
public void testReduce(){
// 计算一下学生的总分数
Double sum = students.stream()
.map(StudentDTO::getScope)
// scope1 和 scope2 表示循环中的前后两个数
.reduce((scope1,scope2) -> scope1+scope2)
.orElse(0D);
log.info("总成绩为 {}",sum);
Double sum1 = students.stream()
.map(StudentDTO::getScope)
// 第一个参数表示成绩的基数,会从 100 开始加
.reduce(100D,(scope1,scope2) -> scope1+scope2);
log.info("总成绩为 {}",sum1);
}
运行结果如下:
第二个计算出来的总成绩多了 100,是因为第二个例子中 reduce 是从基数 100 开始累加的。
其它版本
假设我们对一个集合中的值进行求和
JDK8 之前:
int sum = 0;
for (int i : integerList) {
sum += i;
}
JDK8 之后通过 reduce 进行处理
int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
一行就可以完成,还可以使用方法引用简写成:
int sum = integerList.stream().reduce(0, Integer::sum);
reduce 接受两个参数,一个初始值这里是 0,一个 BinaryOperator accumulator 来将两个元素结合起来产生一个新值,
另外, reduce 方法还有一个没有初始化值的重载方法
获取流中最小最大值
通过 min/max 获取最小最大值
Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
也可以写成:
OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();
min 获取流中最小值,max 获取流中最大值,方法参数为 Comparator<? super T> comparator
通过 minBy/maxBy 获取最小最大值
Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));
minBy 获取流中最小值,maxBy 获取流中最大值,方法参数为 Comparator<? super T> comparator
通过 reduce 获取最小最大值
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);
在本节中,你将看到如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
元素求和
在我们研究如何使用reduce方法之前,先来看看如何使用for-each循环来对数字列表中的元素求和:
int sum = 0;
for (int x : numbers) {
sum += x;
}
numbers中的每个元素都用加法运算符反复迭代来得到结果。通过反复使用加法,你把一个数字列表归约成了一个数字。这段代码中有两个参数:
总和变量的初始值,在这里是0;
将列表中所有元素结合在一起的操作,在这里是+。
要是还能把所有的数字相乘,而不必去复制粘贴这段代码,岂不是很好?这正是reduce操作的用武之地,它对这种重复应用的模式做了抽象。你可以像下面这样对流中所有的元素求和:
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce接受两个参数:
一个初始值,这里是0;
一个BinaryOperator
你也很容易把所有的元素相乘,只需要将另一个Lambda:(a, b) -> a * b传递给reduce操作就可以了:
int product = numbers.stream().reduce(1, (a, b) -> a * b);
图5-7展示了reduce操作是如何作用于一个流的:Lambda反复结合每个元素,直到流被归约成一个值。
让我们深入研究一下reduce操作是如何对一个数字流求和的。首先,0作为Lambda(a)的第一个参数,从流中获得4作为第二个参数(b)。0 + 4得到4,它成了新的累积值。然后再用累积值和流中下一个元素5调用Lambda,产生新的累积值9。接下来,再用累积值和下一个元素3调用Lambda,得到12。最后,用12和流中最后一个元素9调用Lambda,得到最终结果21。
图 5-7 使用reduce来对流中的数字求和’
你可以使用方法引用让这段代码更简洁。在Java 8中,Integer类现在有了一个静态的sum方法来对两个数求和,这恰好是我们想要的,用不着反复用Lambda写同一段代码了:
int sum = numbers.stream().reduce(0, Integer::sum);
无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:
Optional
为什么它返回一个Optional
最大值和最小值
原来,只要用归约就可以计算最大值和最小值了!让我们来看看如何利用刚刚学到的reduce来计算流中最大或最小的元素。正如你前面看到的,reduce接受两个参数:
一个初始值
一个Lambda来把两个流元素结合起来并产生一个新值
Lambda是一步步用加法运算符应用到流中每个元素上的,如图5-7所示。因此,你需要一个给定两个元素能够返回最大值的Lambda。reduce操作会考虑新值和流中下一个元素,并产生一个新的最大值,直到整个流消耗完!你可以像下面这样使用reduce来计算流中的最大值,如图5-8所示。
Optional
图 5-8 一个归约操作——计算最大值
要计算最小值,你需要把Integer.min传给reduce来替换Integer.max:
Optional
你当然也可以写成Lambda (x, y) -> x < y ? x : y而不是Integer::min,不过后者比较易读。
测验5.3:归约
怎样用map和reduce方法数一数流中有多少个菜呢?
答案:要解决这个问题,你可以把流中每个元素都映射成数字1,然后用reduce求和。这相当于按顺序数流中的元素个数。
int count = menu.stream()
.map(d -> 1)
.reduce(0, (a, b) -> a + b);
map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名,因为它很容易并行化。请注意,在第4章中我们也看到了内置count方法可用来计算流中元素的个数:
long count = menu.stream().count();