Java 集合抛出或不抛出';t根据集合的内容抛出ConcurrentModificationException

Java 集合抛出或不抛出';t根据集合的内容抛出ConcurrentModificationException,java,concurrentmodification,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.ad

下面的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);
            }
        }
    }
}

这将打印输出“[lalala]”。当第一个示例抛出时,为什么第二个示例不抛出一个
ConcurrentModificationException

您应该从
迭代器中删除
(i)而不是直接从
集合中删除
(c)

试试这个:

for (Iterator<String> i = c.iterator(); i.hasNext();) {
    String s = i.next();
    if(s.equals("lalala")) {
        i.remove(); //note here, changing c to  i with no parameter.
    }
}
for(迭代器i=c.Iterator();i.hasNext();){
字符串s=i.next();
如果(s.equals(“lalala”)){
i、 remove();//注意这里,将c更改为i,不带参数。
}
}
编辑:

第一个try抛出异常而第二个try没有抛出异常的原因仅仅是因为集合中的元素数量

Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
因为第一个将多次通过循环,而第二个将只迭代一次。因此,它没有机会抛出异常

简短回答 因为不能保证迭代器的快速失败行为

长话短说 之所以出现此异常,是因为除了通过迭代器之外,在对集合进行迭代时无法对其进行操作

坏:

// 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);
    }
}
// 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();
    }
}
//我们正在使用迭代器
对于(迭代器i=c.Iterator();i.hasNext();){
//在此,集合将检查它是否未被修改(努力快速失败)
字符串s=i.next();
如果(s.equals(“lalala”)){
//s已从集合中删除,集合将注意到它已被修改
c、 移除(s);
}
}
好:

// 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);
    }
}
// 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();
    }
}
//我们正在使用迭代器
对于(迭代器i=c.Iterator();i.hasNext();){
//在此,集合将检查它是否未被修改(努力快速失败)
字符串s=i.next();
如果(s.equals(“lalala”)){
//s通过迭代器从集合中移除,因此迭代器知道集合已更改,并可以恢复迭代
i、 删除();
}
}

现在转到“为什么”:在上面的代码中,注意修改检查是如何执行的——删除将集合标记为已修改,下一次迭代将检查是否有任何修改,如果检测到集合已更改,则将失败。另一件重要的事情是,
ArrayList
(不确定其他集合)不会在
hasNext()
中检查修改

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

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

请注意,这一切都符合:

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

编辑以添加:
提供一些信息,说明为什么在
hasNext()
中不执行并发修改检查,而只在
next()
中执行并发修改检查。从其他答案中,您知道在迭代集合时删除集合中元素的正确方法。 我在这里对基本问题进行了解释。你的问题的答案在下面的堆栈跟踪中

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)
在stacktrace中,
i.next()行抛出错误。但是当集合中只有两个元素时

Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
Collection c=newarraylist();
c、 添加(“拉拉”);
c、 添加(“拉拉”);
移除维拉拉(c);
系统错误println(c);

当删除第一个异常时,
i.hasNext()
返回false,并且从不执行
i.next()
来抛出异常

如果使用“for each”循环浏览列表,则无法从列表中删除

无法从正在迭代的集合中删除项。您可以通过显式使用迭代器并删除其中的项来解决这个问题。您可以使用迭代器

如果使用以下代码,则不会出现任何异常:

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);
      }
    }*/

    Iterator<String> it = c.iterator();
    while (it.hasNext()) {
        String st = it.next();
        if (st.equals("lalala")) {
            it.remove();
        }
    }
  }
private static void removeLalala(集合c)
{
/*for(迭代器i=c.Iterator();i.hasNext();){
字符串s=i.next();
如果(s.equals(“lalala”)){
c、 移除(s);
}
}*/
迭代器it=c.Iterator();
while(it.hasNext()){
字符串st=it.next();
如果(标准值等于(“拉拉”)){
it.remove();
}
}
}

如果查看
ArrayList
迭代器(私有嵌套类
Itr
)的源代码,您将看到代码中的缺陷

代码应该是快速失败的,这是在迭代器内部通过调用
checkForComodification()
完成的,但是
hasNext()
不会进行该调用,可能是出于性能原因

hasNext()
只是:

public boolean hasNext() {
    return cursor != size;
}
这意味着,当您位于列表的第二个最后一个元素上,然后删除一个元素(任何元素)时,大小将减小,
hasNext()
认为您位于最后一个元素上(您不是),并返回
false
,跳过最后一个元素的迭代,不会出错


哎呀

在这两种情况下,删除功能是相同的。我的问题是为什么在第二种情况下它没有给出错误,但在第一种情况下它给出错误?@RaviKuldeep我已经更新了我的answ