【问题标题】:Unexpected side effect with java streams reduce operation [closed]java流的意外副作用减少操作[关闭]
【发布时间】:2020-01-05 21:38:33
【问题描述】:

在 Stream 上调用“reduce”时,我观察到对基础集合的副作用。这是非常基本的。我不敢相信我所看到的,但我找不到错误。下面是代码和结果输出。谁能向我解释为什么 Stream 的底层集合之一发生了变异?

package top;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<Integer> list2 = new ArrayList<>();
        ArrayList<Integer> list3 = new ArrayList<>();

        list1.addAll(Arrays.asList(1, 2, 3));
        list2.addAll(Arrays.asList(4, 5, 6));
        list3.addAll(Arrays.asList(7, 8, 9));

        System.out.println("Before");
        System.out.println(list1);
        System.out.println(list2);
        System.out.println(list3);

        ArrayList<Integer> r1 = Stream.of(list1, list2, list3).reduce((l, r) -> {
            l.addAll(r);
            return l;
        }).orElse(new ArrayList<>());

        System.out.println("After");
        System.out.println(list1);
        System.out.println(list2);
        System.out.println(list3);

        System.out.println("Result");
        System.out.println(r1);

    }
}

// Output
Before
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
After
[1, 2, 3, 4, 5, 6, 7, 8, 9] // Why is this not [1,2,3]?
[4, 5, 6]
[7, 8, 9]
Result
[1, 2, 3, 4, 5, 6, 7, 8, 9]

【问题讨论】:

  • 废话。我想我意识到这正是它在 Java 中的工作方式。那正确吗?真的吗?
  • 你认为l.addAll(r) 是做什么的?它更新l 列表的内容。那么,为什么您对列表已更新感到困惑?
  • 是的,您明确告诉您的代码这样做:使用l.addAll(r); return l; 您告诉将所有内容添加到左侧列表,然后返回左侧列表。对于每次迭代,左侧列表为list1。好吧,我认为布赖恩的回答已经足够解释了。

标签: java java-8 java-stream side-effects


【解决方案1】:

TStream.reduce 采用(可能是任意的?)元素对并将您的累加器应用于它们。这不是你想要的累加器,因为ArrayList&lt;&gt;.addAll 会改变列表。

一种解决方案是在您的累加器中创建一个新列表并将 args 附加到该列表中,或者将 Stream.collect 函数与您自己的 CollectorCollectors.toCollection(ArrayList::new) 一起使用

这也意味着您的.orElse(new ArrayList&lt;&gt;()) 永远不需要,因为在这种情况下将返回身份。

请注意,从技术上讲,您的函数不是 associative,因此如果您的列表不按顺序运行,它可能不会总是以相同的顺序结束。

【讨论】:

  • 使用其他形式不会否定归约操作是无状态的。如果流以并行模式执行,则每个线程独立使用该单一标识“值”,从而造成严重破坏。你不能这样做,所以你建议的解决方案是有缺陷的。
  • 好的,现在应该是正确的。
  • 该函数与结果相关,只是无法预测哪些列表会受到副作用的影响。应该注意的是,如果不需要特定类型的ListCollectors.toList() 是要走的路。你的回答以TStream 开头,我猜应该是Stream……
【解决方案2】:

您可以使用另一个重载Stream.reduce(T identity, BinaryOperator&lt;T&gt; accumulator),它允许播种累加器,即

ArrayList<Integer> r1 = Stream.of(list1, list2, list3)
   .reduce(new ArrayList<>(), (l, r) -> {l.addAll(r); return l;})

在您的情况下,您没有为累加器提供种子,因此使用第一个可用值。

【讨论】:

    猜你喜欢
    • 2017-10-28
    • 2023-03-08
    • 1970-01-01
    • 2018-06-15
    • 2021-07-29
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    • 2017-03-21
    相关资源
    最近更新 更多