Java 8 - Stream流骚操作解读2_归约操作

2021-08-17 16:47:32 浏览数 (1)


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);

0 人点赞