为什么在此示例中没有得到java.util.ConcurrentModificationException?


176

注意:我知道该Iterator#remove()方法。

在下面的代码示例中,我不明白为什么List.removemain方法抛出ConcurrentModificationException,但不是remove方法。

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
迭代列表时从列表中删除元素的唯一安全方法是使用Iterator#remove()。你为什么要这样呢?
马特·鲍尔,

@MattBall:我只是想看看这里可能是什么原因。因为,这两种方法都是相同的“增强循环”,但是一个抛出,ConcurrentModificationException而另一个则不。
Bhesh Gurung

删除的元素有所不同。在方法中,删除“中间元素”。在主要您删除最后一个。如果交换数字,则方法中会出现异常。仍然不确定为什么。
Ben van Gompel,

我有一个类似的问题,当我的循环又迭代了一个在我删除循环中的项目后不存在的位置。我只是通过return;在循环中添加a来解决此问题。
frank17年

在java8 Android上,删除除最后一个元素以外的其他元素将调用ConcurrentModificationException。因此对于您的情况,remove函数将获得一个异常,该异常与您之前观察到的相反。
gonglong '16

Answers:


262

原因如下:正如Javadoc中所说:

此类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间以任何方式对列表进行结构修改,除非通过迭代器自己的remove或add方法,否则迭代器将抛出ConcurrentModificationException。

该检查是在next()迭代器的方法中完成的(如您在stacktrace中所看到的)。但是,next()仅当hasNext()传递的值为true时,我们才会实现该方法,这是每个方法调用此方法以检查是否满足边界的要求。在您的remove方法中,当hasNext()检查是否需要返回另一个元素时,将看到它返回了两个元素,现在在删除一个元素之后,列表仅包含两个元素。因此,一切都变桃了,我们已经完成了迭代。不会进行并发修改检查,因为这是在next()从未调用过的方法中完成的。

接下来,我们进入第二个循环。在删除第二个数字之后,hasNext方法将再次检查是否可以返回更多值。它已经返回了两个值,但是列表现在只包含一个。但是这里的代码是:

public boolean hasNext() {
        return cursor != size();
}

1!= 2,因此我们继续执行该next()方法,该方法现在意识到有人一直在弄乱列表并触发异常。

希望能解决您的问题。

摘要

List.remove()ConcurrentModificationException从列表中删除倒数第二个元素时不会抛出该异常。


5
@pushy:只能回答似乎可以回答问题的实际内容,并且解释很好。我接受此答案,并且也+1。谢谢。
Bhesh Gurung

42

处理它的一种方法是从副本Collection(不是Collection本身)中删除某些内容(如果适用)。Clone原始集合通过副本复制Constructor

当不允许对对象进行并发修改的方法被检测到时,可能会引发此异常。

对于您的具体情况,首先,我认为final考虑到您打算修改声明后的列表,这不是一种方法

private static final List<Integer> integerList;

还可以考虑修改副本而不是原始列表。

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

删除项目时,forward / iterator方法不起作用。您可以无错误地删除元素,但是当您尝试访问已删除的项目时,将出现运行时错误。您不能使用迭代器,因为如pushy所示,它将导致ConcurrentModificationException,因此请改用常规的for循环,但请向后逐步执行。

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

一个办法:

如果要删除列表元素,请以相反的顺序遍历数组。只需简单地向后浏览列表,就可以避免访问已被删除的项目,从而删除了异常。

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

好主意!
dobrivoje

7

此代码段将始终引发ConcurrentModificationException。

规则是“在使用Iterator进行迭代(在使用for-each循环时发生)时,您不能修改(从列表中添加或删除列表中的元素)”。

JavaDocs:

此类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间以任何方式对列表进行结构修改,除非通过迭代器自己的remove或add方法,否则迭代器将抛出ConcurrentModificationException。

因此,如果要修改列表(或一般的任何集合),请使用迭代器,因为它知道修改后的内容,因此可以正确地进行处理。

希望这可以帮助。


3
OP明确指出,其中一个循环不会引发异常,而疑问的是为什么会发生这种情况。
madth3 2011年

“讯问”是什么意思?
Bhushan

4

我有同样的问题,但是如果我要在迭代列表中添加en元素。我是这样做的

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

现在一切正常,因为您没有在列表上创建任何迭代器,而是“手动”对其进行了迭代。条件i < integerList.size()永远不会愚弄您,因为当您删除/增加列表减量/增量的列表大小时,..

希望对我有帮助,这就是解决方案。


这不是真的 !证据:运行此代码片段以查看结果:public static void main(String ... args){List <String> listOfBooks = new ArrayList <>(); listOfBooks.add(“代码完成”); listOfBooks.add(“代码22”); listOfBooks.add(“ 22有效”); listOfBooks.add(“ Netbeans 33”); System.err.println(“删除前:” + listOfBooks); for(int index = 0; index <listOfBooks.size(); index ++){如果(listOfBooks.get(index).contains(“ 22”)){listOfBooks.remove(index); }} System.err.println(“删除后:” + listOfBooks); }
dobrivoje

1

如果您使用写时复制集合,它将起作用。但是,当您使用list.iterator()时,即使另一个线程修改了该集合,返回的Iterator仍将始终像调用list.iterator()一样引用元素的集合。在基于写时复制的Iterator或ListIterator上调用的任何变异方法(例如add,set或remove)都将引发UnsupportedOperationException。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

在Java 1.6上运行良好

〜%javac RemoveListElementDemo.java〜
%java RemoveListElementDemo〜
%猫RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

〜%


抱歉
打错

嗯...可能是您有其他实现方式。但是,按照规范,应该这样做,IMO。看看@Pushy的答案。
Bhesh Gurung

不幸的是,id不在Java 1.8上
dobrivoje

0

就我而言,我是这样做的:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

将Iterator更改for eachfor loop可解决。

原因是:

此类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间以任何方式对列表进行结构修改,除非通过迭代器自己的remove或add方法,否则迭代器将抛出ConcurrentModificationException。

-参考Java文档。


-1

检查您的代码人。

在主要方法中,您尝试删除不存在的第4个元素,从而导致错误。在remove()方法中,您尝试删除那里的第三个元素,因此没有错误。


您误会了:数字23不是列表的索引,而是元素。两种删除逻辑都equals针对列表元素而不是元素索引进行检查。此外,如果它与索引相关,则为IndexOutOfBoundsException,而不是ConcurrentModificationException
马尔特·哈特维格
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.