【问题标题】:How does the reduce() method work in Java 8?reduce() 方法在 Java 8 中是如何工作的?
【发布时间】:2019-09-22 00:29:28
【问题描述】:

我试图了解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

它是这些字符串长度的总和。而且我看到没有访问组合器,所以它不会乘以数字,它只会添加数字。

但如果我将stream 替换为parallelStream

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

我看到累加器和组合器都被访问了,但只有乘法是返回的。那么总和会发生什么?

【问题讨论】:

  • combiner 仅用于并行流。还请仔细阅读reduce的文档,您不能在accumulator 中进行求和,在combiner 中进行乘法运算并期望得到有意义的结果

标签: java-8 java java-8 java-stream reduce


【解决方案1】:

您应该阅读reduce 的文档:

另外,combiner 函数必须与 accumulator 函数兼容;对于所有 u 和 t,必须满足以下条件:

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

在您的情况下,您违反了该法律(在 accumulator 中进行 sum 并在 combiner 中进行 乘法),因此您看到的结果是一个操作实际上是未定义的,取决于底层源的 Spliterator 是如何实现的(不要那样做!)。

此外,combiner为并行流调用。

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

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

如果您这样做只是为了学习,正确的reduce 将是(获取sum):

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 – 一个接受两个参数的函数:归约操作的部分结果和流的下一个元素
    • Combiner – 一个接受两个参数的函数:归约操作的部分结果和流的下一个元素 组合器 - 当归约并行化或累加器参数的类型与累加器实现的类型不匹配时,用于组合归约操作的部分结果的函数

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

    案例 1:Combiner 与 parallelStream 一起使用,如您的示例所示

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

    在这种情况下,我们有一个用户对象流,累加器参数的类型是整数和用户。但是,累加器的实现是整数的总和,所以编译器无法推断用户参数的类型。

    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) -> {})
    

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

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

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

    【讨论】:

      【解决方案3】:

      使用 有 3 种减少方法。简而言之,Stream::reduce 从两个后续项(或一个标识值与第一个项)开始,并与它们执行操作以产生新的减少值。对于下一个项目,同样的情况会发生,并使用减少的值执行操作。

      假设您有'a''b''c''d' 的流。归约执行以下操作序列:

      1. result = operationOn('a', 'b') - operationOn 可能是任何东西(输入长度​​的总和......)
      2. result = operationOn(result, 'c')
      3. result = operationOn(result, 'd')
      4. result is returned

      方法有:

      【讨论】:

      • 身份值称为“默认值”是一种误导,如果不是更糟的话。如果您需要默认值,请使用reduce(function).orElse(default)。此外,对第三次重载的解释,“加上功能被组合”并没有真正的帮助。第三个版本的要点是,它允许归约到与 Stream 的元素类型不同的结果类型。与直接替代方案stream.map(…).reduce(id, function) 的区别在于,您可以使用优化函数将源元素 (T) 与结果元素 (U) 组合在一起(如果有的话)。
      • “initial”可能比“default”更好,对吧?我仍然提高我的英语。感谢您的留言:)
      • 嗯,“身份价值”这个词本身已经是一个词,没有更好的替代词。 “初始”和“默认”都不够。身份值的契约在文档中进行了解释,但是,正如 this answer 中所解释的,它是一个术语,而不仅仅是为归约操作而发明的。
      【解决方案4】:

      我假设您选择做加法和乘法只是为了看看究竟发生了什么。

      正如您已经注意到的那样,正如已经提到的,组合器仅在并行流上调用。

      简而言之,在并行流上,流的一部分(分别是底层的 Spliterator)被截断并由不同的线程处理。在处理了几个部分后,它们的结果会被组合器组合起来。

      在您的情况下,这四个元素都由不同的线程处理,然后按元素进行组合。这就是为什么你看不到任何加法(除了0 +),而只有乘法。

      但是,为了获得有意义的结果,您应该从* 切换到+,而是进行更有意义的输出。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-25
        • 1970-01-01
        • 2015-08-29
        • 2022-12-07
        • 2015-12-07
        • 2015-07-09
        • 2019-06-27
        相关资源
        最近更新 更多