Получение исключения ConcurrentModificationException при удалении элемента из java.util.List во время итерации списка?

@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.

Похоже, что когда я удаляю указанный элемент из list, list не знает, что его size были изменены.

Мне интересно, это обычная проблема с collections и удалением элементов?


person hguser    schedule 25.02.2011    source источник


Ответы (11)


Я считаю, что это цель Iterator .remove (), чтобы иметь возможность удалить элемент из коллекции во время итерации.

Например:

Iterator<String> iter = li.iterator();
while(iter.hasNext()){
    if(iter.next().equalsIgnoreCase("str3"))
        iter.remove();
}
person Paul Blessing    schedule 25.02.2011
comment
к сожалению, в цикле foreach нет доступа к базовому итератору, поэтому iterator.remove() скрыт. - person akf; 25.02.2011
comment
Ну да, вы не сможете использовать цикл for-each, вам придется переключиться на использование Iterator. - person Paul Blessing; 25.02.2011
comment
@afk, да надо бы переходить на итератор. Помните, что цикл for-each - это просто синтаксический сахар. Это был бы один из тех случаев, когда явно не имеет смысла использовать такой синтаксис. - person Tim Bender; 25.02.2011
comment
iter.remove () также вызывает это исключение. - person Amalgovinus; 04.04.2016

Способ Java 8 удалить его из списка без итератора:

li.removeIf(<predicate>)

i.e.

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

Обратите внимание, что это исключение не всегда означает, что объект был одновременно изменен другим потоком. Если один поток выдает последовательность вызовов метода, которая нарушает контракт объекта, объект может вызвать это исключение. Например, если поток изменяет коллекцию напрямую во время итерации по коллекции с помощью отказоустойчивого итератора, итератор покажет это исключение.

Взято с http://download.oracle.com/javase/1.4.2/docs/api/java/util/ConcurrentModificationException.html

person Serhiy    schedule 25.02.2011

да, люди сталкиваются с этим - проблема в том, что вы не можете изменить список во время итерации по нему. Раньше я использовал 2 альтернативы:

  1. Вы можете отслеживать индексы элементов, которые хотите удалить, а затем удалять их после завершения итерации.
  2. Или вы можете скопировать все те, которые хотите сохранить, в новый список по мере выполнения итерации, а затем отбросить старый список, когда закончите.

эти параметры предполагают, что вам нужно перебирать список, чтобы найти элементы для удаления - полезно в тех случаях, когда элементы списка являются сложными объектами со свойствами, которые вы можете протестировать.

В вашем конкретном случае вам даже не нужно повторять, так как вы можете просто использовать removeAll. Посмотрите на API здесь. Есть также изящные методы, такие как keepAll, которые отбрасывают все, чего нет в аргументе. Вы можете использовать методы, подобные удалению / сохранению, когда объекты в списке правильно реализуют equals и hashcode. Если вы не можете полагаться на equals / hashcode для определения равенства между экземплярами в вашем приложении, вам придется выполнить удаление самостоятельно ....

person hvgotcodes    schedule 25.02.2011

Попробуйте это (Java 8):

list.removeIf(condition);
person Taras Melnyk    schedule 01.11.2017

Вы можете сделать копию списка, из которого хотите удалить элемент, непосредственно в цикле for-each. Для меня это самый простой способ. Что-то вроде этого:

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

Надеюсь, это поможет вам ..

person stakahop    schedule 14.06.2017

Думаю, стоит упомянуть версию 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);
}
person gdogaru    schedule 18.04.2014
comment
или просто используйте _1 _... - person user2336315; 23.06.2014

В ArrayList есть поле modCount - количество модификаций коллекции

При вызове метода iterator() создается новый объект Itr. В нем есть поле expectedModCount. Поле expectedModCount инициализируется значением modCount. Когда вы вызываете

li.remove("str3");

modCount шагов. Когда вы пытаетесь получить доступ к li через итератор, проверяет, что expectedModCount == modCount

и если он ложный, бросает ConcurrentModificationException

Следовательно, если вы получаете итератор и после изменения коллекции - итератор считается недействительным, и вы не можете его использовать.

person gstackoverflow    schedule 11.03.2014

Я думаю, что лучший ответ - от 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;
  });
person rpajaziti    schedule 05.07.2017

У меня возникла эта проблема, и я думаю, что более простой способ такой же, как и второй способ, который дал 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);
}
person Envil    schedule 05.12.2012

Я зациклил по другому ...

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);
}
person Mark Meyers    schedule 15.02.2017