likes
comments
collection
share

一文搞懂 java8 reduce操作

作者站长头像
站长
· 阅读数 15

什么是 reduce

Java8 中有两大最为重要的改变,其一是 Lambda 表达式,另一个就是 Stream API 了。

Stream 是 Java8 中处理集合的关键抽象概念,它将数据源流化后,可以执行非常复杂的查找、过滤和映射数据、排序、切片、聚合统计等操作。操作之后会产生一个新的流,而数据源则不会发生改变。

在使用 Stream 操作的过程中,往往有三个步骤,

1. 创建 Stream

从一个数据源(集合,数组)中,新建一个 Stream 流。

2. 中间操作

一个中间操作链,对 Stream 流的数据进行处理。比如查找、过滤、映射转换等。

3. 终止操作

一个终止操作,执行中间操作链,并产生结果。常用的终止操作有 forearch、collect、match、count、min、max、reduce 等。

其中本文主要讲解的 reduct 操作就属于是 Stream 流操作中的终止操作。


reduce 操作是一种通用的归约操作,它可以实现从 Stream 中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。

比如终止操作中提到 count、min 和 max 方法,因为常用而被纳入标准库中。事实上这些方法都是一种 reduce 操作。

本文大纲如下,

一文搞懂 java8 reduce操作

reduce 操作三要素

为了方便大家理解 reduce (规约)操作,先给大家演示一下 reduce 操作的相关代码示例,

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
  .stream()
  .reduce(0, (subtotal, element) -> subtotal + element);
assertThat(result).isEqualTo(21);

可以看到,我们的 reduce 操作接受了三个参数,返回了一个 int 基本类型。

在 Stream API 中,提供了三个 reduct 操作方法,根据参数不同进行区分。

一文搞懂 java8 reduce操作

对应上方代码示例,也就是使用了接受两个参数的 reduce 方法,但其实接受两个参数的 reduce 方法的代码逻辑是和接受三个参数的 reduce 方法是一致的。通过上方截图可以看出。

所以这里,我就直接给大家介绍下 reduce 操作的三个参数分别有什么作用即可。

identiy 参数

identiy(初始值)是 reduce 操作的初始值,也就是当元素集合为空时的默认结果。对应上方代码示例,也就是说 reduce 操作的初始值是 0。

accumulator 参数

accumulator(累加器)是一个函数,它接受两个参数,reduce 操作的部分元素和元素集合中的下一个元素。它返回一个新的部分元素。在这个例子中,累加器是一个 lambda 表达式,它将集合中两个整数相加并返回一个整数:(a, b) -> a + b。

combiner 参数

combiner(组合器)是一个函数,它用于在 reduce 操作被并行化或者当累加器的参数类型和实现类型不匹配时,将 reduce 操作的部分结果进行组合。在上面代码示例中,我们不需要使用组合器,因为上面我们的 reduce 操作不需要并行,而且累加器的参数类型和实现类型都是 Integer。


为了方便大家理解 reduce 操作的内部逻辑,我给大家绘制了上面代码示例的执行示意图,如下,

一文搞懂 java8 reduce操作

如何使用 reduce 操作

为了更好地理解初始值,累加器和组合器的功能,让我们看一些基本的例子。

使用 reduce 查询整数集合的最小值

// 创建一个整数集合
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

// 找出集合中的最小值
Integer min = numbers.stream().reduce((integer, integer2) -> {
    if (integer < integer2) {
        return integer;
    } else {
        return integer2;
    }
}).get();

// 输出结果
System.out.println(min); // 1

在这个例子中,我们使用了一个参数的 reduce 操作,它接受一个累加器函数。累加器函数会返回集合两个元素中,较小的元素。

最终我们就可以找出集合中最小值 1。

使用 reduce 操作拼接字符串列表

我们可以对一个字符串列表使用 reduce 操作,将它们拼接成一个单一的字符串:

// 创建一个字符串列表
List<String> letters = Arrays.asList ("a", "b", "c", "d", "e");

// 使用 reduce 操作拼接字符串列表
String result = letters
  .stream ()
  .reduce ("", (partialString, element) -> partialString + element);

// 输出结果
System.out.println (result); // abcde

在这个例子中,我们将初始值设为 "",累加器函数设为 (a, b) -> a + b,它表示将两个字符串拼接起来。

我们可以看到,reduce 操作将累加器函数反复应用到列表中的每个元素上,得到最终的结果 abcde。

使用并行流计算整数列表的总和

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 56);

// 使用并行流和 reduce() 方法计算整数列表的总和
int result = numbers.parallelStream()
        .reduce(0, (a, b) -> a + b, Integer::sum);

// 输出结果
System.out.println(result); // 21

在这个例子中,我们使用 parallelStream() 方法将列表转换为并行流,再使用 reduce() 方法对整数列表进行 reduce 操作,并使用 Integer::sum 作为合并函数 combiner,将并行计算的结果合并。

使用并行流的好处能够充分利用多核 CPU 的优势,使用多线程加快对集合数据的处理速度。

不过并行流也不是任何时候都可以使用的,并行流执行过程中实际按照多线程执行,多线程编程有的问题,并行流都有。

比如多线程的线程安全,执行顺序等问题,并行流都是有的。这一点需要大家注意。

最后聊两句

本文介绍了 Java8 Stream 流中,reduce 操作的相关概念和接收参数,包含初始值,累加器和组合器,最后介绍了 reduce 操作如何使用,希望大家喜欢。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!