我试图理解为什么当我在下面的代码片段中将 y.addAll(x) 更改为 x.addAll(y) 时会出现不同:
List<Integer> result = List.of(1, 2)
.parallelStream()
.collect(
ArrayList::new,
(x, y) -> x.add(y),
(x, y) -> y.addAll(x)
);
System.out.println(result);
我知道,当我使用 parallelStream() 时,一次会有多个线程运行。
collect() 有三个参数;前两个参数我理解。对于第三个参数,我知道 x、y 是子流,并且它们是 ArrayList,但我不明白为什么每种情况下的结果都不同。我期望它们是相同的。
(x, y) -> y.addAll(x) // 输出:[1]
(x, y) -> x.addAll(y) // 输出:[1, 2]
为什么一个是正确的,另一个不是
从Javadocs
Stream#collect
(特别是最后一个参数,重点是我的):类似地,将从到 的
a.addAll(b)
所有元素相加,但反向操作则不行。它从参数中获取信息并修改接收器。b
a
因此,该方法的契约规定您必须将 lambda 的第二个参数合并到第一个参数中。
如果您这样做
(x, y) -> x.addAll(y)
,它将按照合同y
将所有元素添加到。但是,如果您将其添加到第二个元素,则会导致未添加的元素,结果中会缺少这些元素。x
(x, y) -> y.addAll(x)
y
x
会发生什么
之所以这样做,是因为并行流将处理拆分成块,不同的线程处理不同的块。处理完成后,需要将元素合并在一起,这是使用组合器(最后一个 lambda 表达式,也就是您谈到的那个)完成的。这个组合器需要能够将两个元素组合在一起,然后使用第一个参数进行进一步处理,而第二个参数则被丢弃。
假设我们有数字 1 和 2(如您的示例所示),并假设一个线程处理包含 1 的块,另一个线程处理包含 2 的块。收集时,每个线程都首先按照代码中的 创建一个新线程
ArrayList
。ArrayList::new
然后,线程将其对应块的元素添加到列表中,从而产生两个列表,每个列表都有一个元素(第一个线程为 1,另一个线程为 2)。当两个线程都完成后,将调用组合器来合并/组合结果。使用x.addAll(y)
,它将第二个列表添加到第一个列表,然后返回正确的结果。但是,使用y.addAll(x)
,它将第一个列表的元素添加到第二个列表,但 Java 假定您想要第一个列表(因为这是您应该修改的),因此 collect 返回第一个列表,其中不包含第二个线程处理的元素。我重命名了一些变量,以便更好地说明发生了什么。
该方法
Stream::collect
返回thisList
。当您将调用的第三个参数更改为
collect()
如下形式时:thisList
将的内容添加到theOtherList
,并且thisList
保持不变,这意味着 的内容theOtherList
不会添加到其中。结果只是
1
巧合。