Java ConcurrentModificationException:如果列表中的null为´;这不是第一个变量
我得到了这个代码片段,它工作得很好Java ConcurrentModificationException:如果列表中的null为´;这不是第一个变量,java,loops,arraylist,concurrentmodification,Java,Loops,Arraylist,Concurrentmodification,我得到了这个代码片段,它工作得很好 import java.util.ConcurrentModificationException; import java.util.*; ArrayList<Object> s = new ArrayList<>(); s.add(null); s.add("test"); try { System.out.println(s + "\n"); for (Object t
import java.util.ConcurrentModificationException;
import java.util.*;
ArrayList<Object> s = new ArrayList<>();
s.add(null);
s.add("test");
try {
System.out.println(s + "\n");
for (Object t : s) {
System.out.println(t);
if (t == null || t.toString().isEmpty()) {
s.remove(t);
System.out.println("ObjectRemoved = " + t + "\n");
}
}
System.out.println(s + "\n");
} catch (ConcurrentModificationException e) {
System.out.println(e);
} catch (Exception e) {
System.out.println(e);
}
到
代码抛出一个ConcurrentModificationException
因此,我的问题是,如果null是列表中的第一个对象,为什么我可以删除null,而如果它是第二个对象,为什么我不能删除null?首先要理解的是,代码是无效的,因为它在遍历列表时在结构上修改了列表,这是不允许的(注意:这是一个轻微的简化,因为有很多方法可以修改列表,但这不是其中之一) 需要了解的第二件事是,
ConcurrentModificationException
在尽最大努力的基础上工作:
Fail-fast迭代器尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的正确性的程序是错误的:迭代器的Fail-fast行为应仅用于检测错误
因此,无效代码可能会或可能不会引发ConcurrentModificationException
——这实际上取决于Java运行时,可能取决于平台、版本等
例如,当我在本地尝试[test,null,null]
时,我没有得到异常,但也得到一个无效的结果,[test,null]
。这与您观察到的两种行为不同
有几种方法可以修复代码,其中最常见的可能是。发生此异常是因为在列表上迭代时删除元素。若要解决此问题,可以使用如下迭代器:
for (Iterator iterator = s.iterator(); iterator.hasNext(); ) {
Object eachObject = iterator.next();
if (eachObject == null || eachObject.toString().isEmpty()) {
iterator.remove();
System.out.println("ObjectRemoved = " + eachObject + "\n");
}
}
好的,在最好的情况下,
ConcurrentModificationException
应该在这两种情况中的任何一种情况下抛出,但事实并非如此(请参阅下面文档中的引用)
如果使用for-each循环在Iterable
上迭代,则数据结构的Iterator()
方法返回的Iterator
将在内部使用(for-each循环仅为一个循环)
现在,您不应该(从结构上)修改在创建此迭代器实例后正在迭代的Iterable
(除非您使用迭代器的remove()
方法,否则这是非法的)。这就是并发修改的含义:在同一数据结构上有两个不同的透视图。如果从一个透视图(list.remove(object)
)对其进行修改,则另一个透视图(迭代器)不会意识到这一点
元素为null
,这与无关。如果更改代码以删除字符串,也会发生同样的情况:
arraylists=newarraylist();
s、 添加(“测试”);
s、 添加(空);
试一试{
System.out.println(s+“\n”);
用于(对象t:s){
系统输出打印ln(t);
如果(t!=null&&t.equals(“测试”)){
s、 移除(t);
System.out.println(“ObjectRemoved=“+t+”\n”);
}
}
System.out.println(s+“\n”);
}捕获(ConcurrentModificationException e){
系统输出打印ln(e);
}捕获(例外e){
系统输出打印ln(e);
}
现在,这种行为在某些场景中不同的原因很简单,如下所示:
这个类的迭代器和listIterator方法返回的迭代器是快速失效的:如果在迭代器创建后的任何时候,列表在结构上被修改,除了通过迭代器自己的remove或add方法,迭代器将抛出ConcurrentModificationExceptionDeterator会快速、干净地失败,而不是在未来的不确定时间冒任意、不确定行为的风险
请注意,无法保证迭代器的fail-fast行为,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬保证。fail-fast迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于它的正确性是一个例外:迭代器的快速失效行为应该只用于检测bug
奇怪的行为是由于迭代器的实现方式造成的。for-each循环将使用ArrayList.iterator()对集合进行迭代
Iterator<Object> obj = s.iterator();
while(obj.hasNext()){
Object t = obj.next();
// the rest of the code you have.
}
在第一次迭代时调用next
,并将光标更新为1。然后删除空值。在随后调用hasNext
时,光标等于大小,循环停止。缺少最后一次迭代,但未抛出CME
现在,在第二种情况下。null是列表中的最后一项,当循环再次开始时,调用hasNext
,它返回true,因为光标大于列表的大小!然后在迭代器时发生CME。调用next
。因为您在迭代其他对象时正在修改列表您需要使用显式迭代器并使用iterator.remove()
。所以你要说的是,在迭代列表时永远不要修改它。所以我想最好的办法是,创建一个没有空值的新列表,然后在迭代后将值移动到另一个列表,对吗?@LukasForman:考虑这一点的方法是,结构修改会使所有迭代器失效。因此,按索引迭代可以(只要您考虑删除元素如何影响其余元素的索引)。使用Iterator.remove()删除元素也可以
。好的,我想我知道了。谢谢你的帮助。啊,现在我花了这么长时间才键入我的答案,我没有意识到已经有一个有效的答案。无论如何,我想我添加了一些关于for each和Iterable
之间关系的细节,所以我将把答案留给任何想了解它的人。+1Your['test',null,null]结果与[null]相同
for (Iterator iterator = s.iterator(); iterator.hasNext(); ) {
Object eachObject = iterator.next();
if (eachObject == null || eachObject.toString().isEmpty()) {
iterator.remove();
System.out.println("ObjectRemoved = " + eachObject + "\n");
}
}
Iterator<Object> obj = s.iterator();
while(obj.hasNext()){
Object t = obj.next();
// the rest of the code you have.
}
public boolean hasNext() {
return cursor != size;
}