Получение ConcurrentModificationException, вызванного при удалении элемента из java.утиль.Список во время итерации списка? [дубликат]


этот вопрос уже есть ответ здесь:

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

11 ответов:

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

например:

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

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

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

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

li.removeIf(<predicate>)

т. е.

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

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

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

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

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

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

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

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

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

li.remove("str3");

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

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

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

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

list.removeIf(condition);

Я сделал петлю по-другому...

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

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

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

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

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