Подтвердить что ты не робот

Получение исключения 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.

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

Мне интересно, является ли это общей проблемой с коллекциями и удалением элементов?

4b9b3361

Ответ 1

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

Например:

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

Ответ 2

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

li.removeIf(<predicate>)

т.е.

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Ответ 5

Я думаю, что стоит упомянуть версию 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);
}

Ответ 6

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

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

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

Ответ 7

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

list.removeIf(condition);

Ответ 8

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

Ответ 9

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

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

li.remove("str3");

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

и если это ложные броски ConcurrentModificationException

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

Ответ 10

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

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

Ответ 11

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