java 流减少操作的意外副作用
Unexpected side effect with java streams reduce operation
我观察到在 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]
TStream.reduce
接受(可能是任意的?)成对的元素并将你的累加器应用于它们。这不是你想要的累加器,因为 ArrayList<>.addAll
改变了列表。
一种解决方案是在您的累加器中创建一个新列表并将参数附加到该列表中,或者将 Stream.collect
函数与您自己的 Collector
或 Collectors.toCollection(ArrayList::new)
[= 一起使用18=]
这也意味着永远不需要您的 .orElse(new ArrayList<>())
,因为在这种情况下将返回身份。
请注意,从技术上讲,您的函数不是 associative,因此如果列表未按顺序 运行,您的列表可能不会总是以相同的顺序结束。
您可以使用 Stream.reduce(T identity, BinaryOperator<T> accumulator)
的另一个重载,它允许为累加器设置种子,即
ArrayList<Integer> r1 = Stream.of(list1, list2, list3)
.reduce(new ArrayList<>(), (l, r) -> {l.addAll(r); return l;})
在您的情况下,您没有为累加器提供种子,因此使用第一个可用值。
我观察到在 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]
TStream.reduce
接受(可能是任意的?)成对的元素并将你的累加器应用于它们。这不是你想要的累加器,因为 ArrayList<>.addAll
改变了列表。
一种解决方案是在您的累加器中创建一个新列表并将参数附加到该列表中,或者将 Stream.collect
函数与您自己的 Collector
或 Collectors.toCollection(ArrayList::new)
[= 一起使用18=]
这也意味着永远不需要您的 .orElse(new ArrayList<>())
,因为在这种情况下将返回身份。
请注意,从技术上讲,您的函数不是 associative,因此如果列表未按顺序 运行,您的列表可能不会总是以相同的顺序结束。
您可以使用 Stream.reduce(T identity, BinaryOperator<T> accumulator)
的另一个重载,它允许为累加器设置种子,即
ArrayList<Integer> r1 = Stream.of(list1, list2, list3)
.reduce(new ArrayList<>(), (l, r) -> {l.addAll(r); return l;})
在您的情况下,您没有为累加器提供种子,因此使用第一个可用值。