Collection根据Collection的内容抛出或不抛出ConcurrentModificationException

Rav*_*eep 17 java concurrentmodification

以下Java代码ConcurrentModificationException按预期抛出:

public class Evil
{
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("lalala");
        c.add("sososo");
        c.add("ahaaha");
        removeLalala(c);
        System.err.println(c);
    }
    private static void removeLalala(Collection<String> c) 
    {
        for (Iterator<String> i = c.iterator(); i.hasNext();) {
            String s = i.next();
            if(s.equals("lalala")) {
                c.remove(s);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是下面的示例(仅在内容中有所不同)Collection执行时没有任何异常:

public class Evil {
    public static void main(String[] args) 
    {
        Collection<String> c = new ArrayList<String>();
        c.add("lalala");
        c.add("lalala");
        removeLalala(c);
        System.err.println(c);
    }
    private static void removeLalala(Collection<String> c) {
        for (Iterator<String> i = c.iterator(); i.hasNext();) {
            String s = i.next();
            if(s.equals("lalala")) {
                c.remove(s);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这将打印输出"[lalala]".ConcurrentModificationException第一个例子的第二个例子为什么不抛出?

Jir*_*sek 22

简短的回答

因为迭代器的快速失败行为无法保证.

答案很长

您将获得此异常,因为除非通过迭代器,否则在迭代时不能操作集合.

坏:

// we're using iterator
for (Iterator<String> 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

好:

// we're using iterator
for (Iterator<String> 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在转到"为什么":在上面的代码中,注意如何执行修改检查 - 删除将集合标记为已修改,下一次迭代检查任何修改,如果检测到集合已更改则失败.另一个重要的事情是,ArrayList(不知道其他收藏品),并不能为您在修改hasNext().

因此,可能会发生两件奇怪的事情:

  • 如果在迭代时删除最后一个元素,则不会抛出任何内容
    • 那是因为没有"next"元素,所以迭代在到达修改检查代码之前结束
  • 如果你删除倒数第二个元素,ArrayList.hasNext()实际上也会返回false,因为迭代器current index现在指向最后一个元素(前者倒数第二个).
    • 所以即使在这种情况下,删除后也没有"下一个"元素

请注意,这一切都与ArrayList的文档一致:

请注意,迭代器的故障快速行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证.失败快速迭代器会尽最大努力抛出ConcurrentModificationException.因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应该仅用于检测错误.

编辑添加:

此问题提供了有关执行并发修改检查的原因hasNext()以及仅在执行中执行的一些信息next().