注意:与此问题不重复:Why am I not getting a java.util.ConcurrentModificationException in this example?。问题是,为什么异常不是被抛出。
如果我们在foreach
上使用List<String>
并尝试从中删除任何元素,那么它会抛出java.util.ConcurrentModificationException
,但为什么以下代码不会抛出相同的异常而且也不会处理第二个对象User
?
public class Common {
public static void main(String[] args) {
User user1 = new User();
user1.setFirstname("Vicky");
user1.setLastname("Thakor");
User user2 = new User();
user2.setFirstname("Chirag");
user2.setLastname("Thakor");
List<User> listUser = new ArrayList<User>();
listUser.add(user1);
listUser.add(user2);
int count = 0;
for (User user : listUser) {
System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
count++;
listUser.remove(user);
}
}
}
输出结果为:
0:Vicky Thakor
答案 0 :(得分:4)
虽然问题并非完全重复,但链接的问题:Why am I not getting a java.util.ConcurrentModificationException in this example?包含答案。
当迭代器的next()
方法调用时,会进行验证是否抛出异常的检查,但仅当hasNext()
返回true
时才会发生这种情况。在您的情况下,当列表包含两个元素并在第一次迭代中删除第一个元素时,条件为:
public boolean hasNext() {
return cursor != size(); // 1 != 1 after first iteration
}
意外false
,因为cursor
位于1
,而size()
目前是next()
。因此不会调用listUser.add(user2);
,并且不会抛出异常。
添加第三个元素:
{{1}}
并抛出异常。但是,您不应该依赖于这种行为,因为正如the documentation所解释的那样,不能保证:
请注意,无法保证快速失败的行为,因为一般来说,在存在不同步的并发修改时,无法做出任何硬性保证。失败快速操作会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的正确性是错误的:ConcurrentModificationException应该仅用于检测错误。
答案 1 :(得分:2)
正如NESPowerGlove在注释部分中所述,interator根据java doc返回一个快速失败的迭代器,但它包含了这个
请注意,无法保证迭代器的快速失败行为 一般来说,不可能做出任何硬性保证 存在不同步的并发修改。快速失败 迭代器抛出ConcurrentModificationException就是尽力而为 基础。因此,编写一个依赖的程序是错误的 关于它的正确性的这个例外:快速失败的行为 迭代器应该只用于检测错误。
(强调我的) 因此,无法保证在修改的情况下会抛出异常。
答案 2 :(得分:0)
您的循环可以重写为
Iterator<User> iterator = listUser.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
listUser.remove(user);
}
如果我们展开循环,它将看起来像
Iterator<User> iterator = listUser.iterator();
System.out.println(iterator.hasNext()); // prints true, loop executes
User user = iterator.next();
System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
listUser.remove(user);
System.out.println(iterator.hasNext()); // prints false, loop stops
直到第二次调用iterator.hasNext(),才会修改集合,因此所有工作都按预期工作。现在问题是为什么第二个iterator.hasNext()调用返回false而不是抛出ConcurrentModificationException?让我们检查一下ArrayList来源。我将引用JDK 8来源。
ArrayList.java,ArrayList&#39;迭代器,在Itr类的第840行声明。它的hasNext()方法非常简单:
int cursor; // index of next element to return
...
public boolean hasNext() {
return cursor != size;
}
cursor是要返回的下一个元素的索引,size属于外部ArrayList实例。
检查next()方法中实现的编码。
请注意,您不得在自己的代码中依赖此检查。它旨在打击错误,而不是为您提供依赖的逻辑。并且它无法保证能够捕获所有错误(如您的问题所示)。可能性能是为什么在hasNext()方法中没有实现此检查的原因。
答案 3 :(得分:0)
试试这个:
int count = 0;
for (User user : listUser) {
listUser.remove(user);
System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
count++;
}
for (User user : listUser) {
listUser.remove(user);
System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
count++;
}
我在不到10秒的时间内创建了User
类。包含两个字符串。但是对于这个问题,User
类是无关紧要的。假设Object
并且问题仍然存在。关键是在调试时运行它,看看会发生什么。
如果您为每个循环添加相同的内容并尝试迭代同一个列表,您将看到ConcurrentModificationException
。第二次,它会显示:
0:Vicky Thakor
1:Chirag Thakor
然后抛出异常。很多人都没记住ConcurrentModificationException
是RuntimeException
,因此,remove()
方法上的API没有记录。这里的问题是你应该信任文档。迭代时删除元素的文档安全方法是使用Iterator
。