Collection根据Collection的内容抛出或不抛出ConcurrentModificationException
以下Java代码按预期抛出ConcurrentModificationException
:
public class Evil { public static void main(String[] args) { Collection c = new ArrayList(); c.add("lalala"); c.add("sososo"); c.add("ahaaha"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection c) { for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } }
但是下面的示例(仅在Collection
的内容上有所不同)执行时没有任何exception:
public class Evil { public static void main(String[] args) { Collection c = new ArrayList(); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection c) { for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } }
这将打印输出“[lalala]”。 为什么第二个示例在第一个示例执行时抛出ConcurrentModificationException
?
简短的回答
因为迭代器的失败快速行为无法保证。
答案很长
您将获得此exception,因为除非通过迭代器,否则在迭代时不能操作集合。
坏:
// we're using iterator for (Iterator i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn't been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection and the collection will take note it was modified c.remove(s); } }
好:
// we're using iterator for (Iterator i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn't been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration i.remove(); } }
现在转到“为什么”:在上面的代码中,注意如何执行修改检查 – 删除将集合标记为已修改,下一次迭代检查任何修改,如果检测到集合已更改则失败。 另一个重要的事情是ArrayList
(不确定其他集合)不检查hasNext()
修改。
因此,可能会发生两件奇怪的事情:
- 如果在迭代时删除最后一个元素,则不会抛出任何内容
- 那是因为没有“next”元素,所以迭代在到达修改检查代码之前结束
- 如果删除倒数第二个元素,
ArrayList.hasNext()
实际上也会返回false
,因为迭代器的current index
现在指向最后一个元素(从倒数第二个到前)。- 所以即使在这种情况下,删除后也没有“下一个”元素
请注意,这一切都与ArrayList的文档一致 :
请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器会尽最大努力抛出ConcurrentModificationException。 因此,编写依赖于此exception的程序以确保其正确性是错误的:迭代器的快速失败行为应该仅用于检测错误。
编辑添加:
此问题提供了有关为什么并行修改检查未在hasNext()
执行且仅在next()
执行的一些信息。
如果你看一下ArrayList
迭代器的源代码(私有嵌套类Itr
),你会看到代码中的缺陷。
代码应该是快速失败的,这是通过调用checkForComodification()
在迭代器内部完成的,但是由于性能原因, hasNext()
不会进行该调用。
而hasNext()
只是:
public boolean hasNext() { return cursor != size; }
这意味着当你在列表的倒数第二个元素上,然后删除一个元素(任何元素)时,大小会减小,并且hasNext()
认为你在最后一个元素上(你不是),并返回false
,跳过最后一个元素的迭代而没有错误。
OOPS !!!!
从其他答案中,您知道在迭代集合时删除集合中元素的正确方法是什么。 我在这里给出了基本问题的解释。你的问题的答案在于下面的堆栈跟踪
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at com.ii4sm.controller.Evil.removeLalala(Evil.java:23) at com.ii4sm.controller.Evil.main(Evil.java:17)
在堆栈跟踪中很明显i.next();
line抛出错误。 但是当你在集合中只有两个元素时。
Collection c = new ArrayList (); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c);
当第一个被删除时, i.hasNext()
返回false,并且永远不会执行i.next()
来抛出exception
你应该直接从iterator
(i)中删除collection
(c);
尝试这个:
for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { i.remove(); //note here, changing c to i with no parameter. } }
编辑:
第一个尝试抛出exception而第二个抛出exception的原因仅仅是因为集合中的元素数量。
因为第一个将不止一次通过循环,第二个将只迭代一次。 因此,它没有机会抛出exception
如果您使用“for each”循环浏览它,则无法从列表中删除。
您无法从正在迭代的集合中删除项目。 你可以通过显式使用Iterator并删除那里的项目来解决这个问题。 你可以使用Iterator。
如果您使用以下代码,您将不会得到任何例外:
private static void removeLalala(Collection c) { /*for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } }*/ Iterator it = c.iterator(); while (it.hasNext()) { String st = it.next(); if (st.equals("lalala")) { it.remove(); } } }