reduce() 方法在 Java 8 中是如何工作的?

2022-09-01 17:02:34

我试图理解该方法在中是如何工作的。reduce()

例如,我有这个代码:

public class App {

    public static void main(String[] args) {
        String[] arr = {"lorem", "ipsum", "sit", "amet"};
        List<String> strs = Arrays.asList(arr);

        int ijk = strs.stream().reduce(0, 
            (a, b) -> { 
                System.out.println("Accumulator, a = " + a + ", b = " + b);
                return a + b.length();
            },
            (a, b) -> {
                System.out.println("Combiner");
                return a * b;
            });
        System.out.println(ijk); 
    }
}

输出是这样的:

Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17

它是这些字符串长度的总和。我看到没有访问合并器,因此它不会将数字相乘,只会将数字相加。

但是如果我用:streamparallelStream

int ijk = strs.parallelStream().reduce(0, 
    (a, b) -> { 
        System.out.println("Accumulator, a = " + a + ", b = " + b);
        return a + b.length();
    },
    (a, b) -> {
        System.out.println("Combiner");
        return a * b;
    });

System.out.println(ijk); 

这是输出:

Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300

我看到累加器和合并器都被访问,但只有乘法返回。那么总和是怎么回事呢?


答案 1

您应该阅读其中的文档:reduce

此外,合路器功能必须与累加器功能兼容;对于所有 u 和 t,以下必须成立:

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

在你的例子中,你违反了这个定律(在 中做一个乘法),所以你看到的这种操作的结果实际上是未定义的,取决于底层源的拆分器是如何实现的(不要这样做!)。accumulatorcombiner

此外,调用并行流。combiner

当然,您的整个方法可以简化为:

Arrays.asList("lorem", "ipsum", "sit", "amet")
      .stream()
      .mapToInt(String::length)
      .sum();

如果您这样做只是为了学习目的,则正确的方法是(获取):reducesum

strs.parallelStream()
    .reduce(0,
            (a, b) -> {
                  System.out.println("Accumulator, a = " + a + ", b = " + b);
                  return a + b.length();
            },
            (a, b) -> {
                  System.out.println("Combiner");
                  return a + b;
            });

答案 2

关键概念:标识、累加器和组合器

Stream.reduce() 操作:让我们将操作的参与者元素分解为单独的块。这样,我们将更容易理解每个人所扮演的角色。

  • Identity – 一个元素,它是缩减操作的初始值,如果流为空,则为默认结果
  • itemAccumulator – 一个采用两个参数的函数:归约运算的部分结果和流的下一个元素
  • 合并器 – 采用两个参数的函数:约简运算的部分结果和流合路器的下一个元素 – 用于在归约并行化时或累加器参数类型与累加器实现的类型不匹配时合并约简运算的部分结果的函数

当流并行执行时,Java 运行时会将流拆分为多个子流。在这种情况下,我们需要使用一个函数将子流的结果组合成一个函数。这就是合路器的作用

案例1 :组合器的工作原理如示例中所示parallelStream

案例2:具有不同类型参数的示例累加器

在本例中,我们有一个 User 对象流,累加器参数的类型为 Integer 和 User。但是,累加器实现是整数的总和,因此编译器无法推断用户参数的类型。

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());

编译错误

The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})

我们可以通过使用组合器来解决此问题:这是方法引用或使用lambda表达式Integer::sum(a,b)->a+b

int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);

简单地说,如果我们使用顺序流,并且累加器参数的类型与其实现的类型匹配,则不需要使用组合器。