如何将Java8流的元素添加到现有列表中
Collector的Javadoc显示了如何将流的元素收集到新的List中。有没有一种将结果添加到现有ArrayList中的方法?
回答:
osid的答案
显示了如何使用来添加到现有集合forEachOrdered()
。这是对现有集合进行变异的有用且有效的技术。我的答案解决了为什么您不应该使用A
Collector
来突变现有集合的原因。
简短的答案是 ,至少在一般情况下不是这样,您不应该使用a Collector
来修改现有集合。
原因是收集器被设计为支持并行性,即使是在不是线程安全的收集器上也是如此。他们这样做的方法是让每个线程根据自己的中间结果集合独立运行。每个线程获取其自己的集合的方式是调用每次Collector.supplier()
返回一个
集合所需的。
然后,再次以线程受限的方式合并这些中间结果的集合,直到只有一个结果集合为止。这是操作的最终结果collect()
。
来自[Balder一些答案建议使用Collectors.toCollection()
然后传递一个返回现有列表而不是新列表的供应商。这违反了供应商的要求,即每次都返回一个新的空集合。
如其答案中的示例所示,这将适用于简单的情况。但是,它将失败,特别是如果流并行运行。(该库的未来版本可能会以某种无法预料的方式更改,即使在顺序的情况下也会导致其失败。)
让我们举一个简单的例子:
List<String> destList = new ArrayList<>(Arrays.asList("foo"));List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
newList.parallelStream()
.collect(Collectors.toCollection(() -> destList));
System.out.println(destList);
当我运行该程序时,通常会收到一个ArrayIndexOutOfBoundsException
。这是因为多个线程正在对ArrayList
一个线程不安全的数据结构进行操作。好的,让我们使其同步:
List<String> destList = Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));
这将不会再因异常而失败。但是,而不是预期的结果:
[foo, 0, 1, 2, 3]
它给出了如下奇怪的结果:
[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]
这是我上面描述的线程限制的累积/合并操作的结果。在并行流的情况下,每个线程都调用供应商以获取自己的集合以进行中间累积。如果传递的供应商返回
集合,则每个线程会将其结果附加到该集合。由于线程之间没有顺序,结果将以任意顺序附加。
然后,当这些中间集合被合并时,这基本上将列表与其自身合并。使用合并列表List.addAll()
,表示如果在操作过程中修改了源集合,则结果是不确定的。在这种情况下,请ArrayList.addAll()
执行阵列复制操作,因此最终会自我复制,这大概是我期望的。(请注意,其他List实现可能具有完全不同的行为。)无论如何,这解释了奇怪的结果和目标中重复的元素。
您可能会说:“我将确保按顺序运行流”,然后继续编写这样的代码
stream.collect(Collectors.toCollection(() -> existingList))
无论如何。我建议不要这样做。当然,如果您控制流,则可以保证它不会并行运行。我希望会出现一种编程风格,即流传递而不是集合传递。如果有人将流交给您,并且您使用此代码,则如果流碰巧是并行的,它将失败。更糟糕的是,有人可能会递给您一个顺序流,并且此代码将在一段时间内正常工作,通过所有测试等。然后,在任意时间后,系统中其他地方的代码可能会更改为使用并行流,这将导致
代码打破。
确定,然后确保sequential()
在使用此代码之前记得记得在任何流上调用:
stream.sequential().collect(Collectors.toCollection(() -> existingList))
当然,您会记得每次都这样做,对吗?:-)假设您愿意。然后,性能团队会想知道为什么他们所有精心设计的并行实现都没有提供任何加速。然后,他们再次将其追溯到
代码,这迫使整个流按顺序运行。
不要这样
以上是 如何将Java8流的元素添加到现有列表中 的全部内容, 来源链接: utcz.com/qa/425754.html