在列表迭代期间从java.util.List中删除元素时抛出ConcurrentModificationException?

时间:2011-02-25 02:40:10

标签: java list concurrentmodification

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(String st:li){
        if(st.equalsIgnoreCase("str3"))
            li.remove("str3");
    }
    System.out.println(li);
}

当我运行此代码时,我将抛出一个ConcurrentModificationException。

看起来当我从列表中删除指定的元素时,列表不知道它的大小已被更改。

我想知道这是集合和删除元素的常见问题吗?

11 个答案:

答案 0 :(得分:88)

我相信这是Iterator.remove()方法背后的目的,是为了能够在迭代时从集合中删除元素。

例如:

Iterator<String> iter = li.iterator();
while(iter.hasNext()){
    if(iter.next().equalsIgnoreCase("str3"))
        iter.remove();
}

答案 1 :(得分:21)

在没有迭代器的情况下从Java中删除它的Java 8方法是:

li.removeIf(<predicate>)

List<String> li = new ArrayList<String>();
// ...
li.removeIf(st -> !st.equalsIgnoreCase("str3"));

答案 2 :(得分:18)

  

请注意,此异常并不总是表示某个对象已被另一个线程同时修改。如果单个线程发出违反对象合同的一系列方法调用,则该对象可能会抛出此异常。例如,如果线程在使用失败快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常

取自http://download.oracle.com/javase/1.4.2/docs/api/java/util/ConcurrentModificationException.html

答案 3 :(得分:6)

是的,人们遇到它 - 问题是你在迭代时无法修改列表。我过去曾使用过两种选择:

  1. 您可以跟踪要删除的项目的索引,然后在完成迭代后删除它们。
  2. 或者,您可以在迭代时将所有要保留的内容复制到新列表中,然后在完成后放弃旧列表。
  3. 这些选项假设您必须遍历列表以查找要删除的元素 - 在列表元素是具有您可能测试的属性的复杂对象的情况下非常有用。

    在您的特定情况下,您甚至不需要迭代,因为您可以使用removeAll。查看API here。还有一些漂亮的方法,比如retainAll,可以丢弃参数中没有的所有内容。只要列表中的对象正确实现equals和hashcode,就可以使用remove / retain-like方法。如果您不能依赖equals / hashcode来识别应用程序中实例之间的相等性,那么您必须自己进行删除....

答案 4 :(得分:2)

我认为值得一提的是Java 8版本

@Test
public void testListCur() {
    List<String> li = new ArrayList<String>();
    for (int i = 0; i < 10; i++) {
        li.add("str" + i);
    }

    li = li.stream().filter(st -> !st.equalsIgnoreCase("str3")).collect(Collectors.toList());

    System.out.println(li);
}

答案 5 :(得分:2)

您可以直接在for-each循环中创建要从中删除元素的列表副本。对我来说,这是最简单的方式。像这样:

for (String stringIter : new ArrayList<String>(myList)) {
    myList.remove(itemToRemove);
}

希望能帮到你..

答案 6 :(得分:2)

试试这个(Java 8):

list.removeIf(condition);

答案 7 :(得分:1)

我遇到了这个问题,我认为更简单的方法与hvgotcodes给出的第二种方式相同。

  

或者您可以在迭代时将所有要保留的内容复制到新列表中,然后在完成后放弃旧列表。

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }
    List<String> finalLi = new ArrayList<String>();
    for(String st:li){
        if(st.equalsIgnoreCase("str3")){
            // Do nothing
        } else {
            finalLi.add(st);
        }
    }
    System.out.println(finalLi);
}

答案 8 :(得分:1)

ArrayList具有字段modCount - 集合修改计数

当您调用方法iterator()时,会创建新对象Itr。它有字段expectedModCountexpectedModCount字段按modCount值初始化。当你调用

li.remove("str3");

modCount增量。您何时尝试通过迭代器访问li 检查expectedModCount == modCount

如果是假,则抛出ConcurrentModificationException

因此,如果你得到iterator并且在修改了集合之后 - 迭代器被认为是无效的,你就不能使用它。

答案 9 :(得分:0)

我用不同的方式...

public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(int i=0; i<li.size(); i++)
        if(li.get(i).equalsIgnoreCase("str3"))
            li.remove(i--);

    System.out.println(li);
}

答案 10 :(得分:0)

我认为最好的答案来自bigdev.de,但我想在其中添加一些内容(例如,如果项目从列表中删除,也许您想在某处或其他地方记录):

List<String> list = new ArrayList<>();

list.removeIf(a -> {
                boolean condition = a.equalsIgnoreCase("some condition");
                if(condition)
                    logger.info("Item removed from the list: " + a);
                return condition;
  });