JL_*_*_SO 6 java lambda list java-8 java-stream
当我运行以下代码时
List<Integer> list = IntStream.range(0,10).boxed().collect(Collectors.toList());
list.stream().forEach(i -> {
System.out.println("i:" +i);
if (i==5) {
System.out.println("..adding 22");
list.add(22);
}
});
Run Code Online (Sandbox Code Playgroud)
我得到以下输出:
i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
i:6
i:7
i:8
i:9
Exception in thread "main" java.util.ConcurrentModificationException
Run Code Online (Sandbox Code Playgroud)
为什么代码超越索引5?我不会在输出中预期以下行:
i:6
i:7
i:8
i:9
Run Code Online (Sandbox Code Playgroud)
关于forEach的行为,我在这里遗漏了一些东西.文档确实声明"此操作的行为明确是不确定的".然后继续讨论并行流.我希望并行流可以以任何顺序执行forEach,但是串行流肯定会串行执行消费者为forEach执行吗?如果是这样,为什么Java允许代码超越索引5处生成的异常?这里有一个主题,对吗?
先感谢您.
编辑:感谢您的答案到目前为止.要明确我的观点,如果我这样做:
for(int i: list){
System.out.println("i:" +i);
if(i==5) {
System.out.println("..adding 22");
list.add(22);
}
}
Run Code Online (Sandbox Code Playgroud)
我明白了:
i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
Exception in thread "main" java.util.ConcurrentModificationException
Run Code Online (Sandbox Code Playgroud)
但我不会在forEach中得到它.所以似乎串行流forEach不是迭代器,无论是手摇(Iterator iter = list.iterator ...)还是增强的for循环迭代器.这对我来说意外.但似乎答案似乎是出于"表现原因".但它仍然......出乎意料.只是为了踢,我尝试了1m元素的列表:
List<Integer> list = IntStream.range(0,1000000).boxed().collect(Collectors.toList());
list.stream().forEach(
i -> {
if(i%250000==0)
System.out.println("i:" +i);
if(i>999997)
System.out.println("i:" +i);
if(i==5) {
System.out.println("..adding 22");
list.add(22);
}}
);
Run Code Online (Sandbox Code Playgroud)
我得到了(现在预期的)以下输出:
i:0
..adding 22
i:250000
i:500000
i:750000
i:999998
i:999999
Exception in thread "main" java.util.ConcurrentModificationException
Run Code Online (Sandbox Code Playgroud)
如上所述,检查似乎是在最后完成的.
您所看到的行为是特定于ArrayListSpliterator所使用的一个Stream过的ArrayList.
代码中的注释解释了实现选择:
我们只
ConcurrentModificationException在最后执行一次检查forEach(性能最敏感的方法)[JDK 8源代码]
这与并发修改检查的合同一致.在修改的情况下,迭代器不需要快速失败.如果他们选择快速失败,实施者可以决定实施检查的严格程度.例如,如上所述,通常需要在正确性和性能之间进行权衡.
检测到并发修改对象的方法可能抛出此异常.[...]失败快速的操作
ConcurrentModificationException是尽最大努力的.因此,编写一个依赖于此异常的程序以确保其正确性ConcurrentModificationException是错误的:应该仅用于检测错误.[Java SE 8 API docs]
那么,有一条评论ArrayListSpliterator:
如果ArrayLists是不可变的,或者在结构上是不可变的(没有添加,删除等),我们可以用Arrays.spliterator实现它们的分裂器.相反,我们在遍历期间检测到尽可能多的干扰,而不会牺牲很多性能
因此,在List每个元素的基础上检查干扰源何时发生(可能,我没有过多考虑实现),但在其他时候,主要目标是不牺牲性能,但仍然源被"编辑"时失败.
您仍在违规non-interference,您的代码仍然会失败,以后,而不是在这种情况下更快.