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(); } } }