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

Примечание. Мне известен метод Iterator#remove().

В следующем примере кода я не понимаю, почему метод List.remove в методе main выдает 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);
            }
        }
    }
}

person Bhesh Gurung    schedule 18.11.2011    source источник
comment
Единственный безопасный способ удалить элемент из списка при переборе этого списка — использовать Iterator#remove(). Почему ты так поступаешь?   -  person Matt Ball    schedule 19.11.2011
comment
@MattBall: я просто пытался понять, в чем может быть причина. Потому что в обоих методах один и тот же улучшенный цикл for, но один выдает ConcurrentModificationException, а другой нет.   -  person Bhesh Gurung    schedule 19.11.2011
comment
Существует разница в элементе, который вы удаляете. В методе вы удаляете «средний элемент». В основном удаляешь последнее. Если вы поменяете числа, вы получите исключение в своем методе. Все еще не уверен, почему это так.   -  person Ben van Gompel    schedule 19.11.2011
comment
У меня была похожая проблема, когда мой цикл повторял также позицию, которая не существовала после того, как я удалил элемент в цикле. Я просто исправил это, добавив в цикл return;.   -  person frank17    schedule 29.04.2016
comment
на java8 Android удаление элемента, кроме последнего, вызовет исключение ConcurrentModificationException. поэтому в вашем случае функция удаления получит исключение, противоположное тому, что вы наблюдали ранее.   -  person gonglong    schedule 14.12.2016
comment
Выброс ConcurrentModificationException не гарантируется.   -  person Raedwald    schedule 13.03.2019


Ответы (10)


Вот почему: Как сказано в Javadoc:

Итераторы, возвращаемые методами iterator и listIterator этого класса, являются отказоустойчивыми: если список структурно изменен в любое время после создания итератора любым способом, кроме как с помощью собственных методов удаления или добавления итератора, итератор выдаст исключение ConcurrentModificationException.

Эта проверка выполняется в методе next() итератора (как видно из трассировки стека). Но мы достигнем метода next() только в том случае, если hasNext() доставлено true, что вызывается для каждого, чтобы проверить, соблюдается ли граница. В вашем методе удаления, когда hasNext() проверяет, нужно ли возвращать другой элемент, он увидит, что он вернул два элемента, и теперь после удаления одного элемента список содержит только два элемента. Итак, все в порядке, и мы закончили итерацию. Проверка одновременных модификаций не происходит, так как это делается в методе next(), который никогда не вызывается.

Далее переходим ко второй петле. После того, как мы удалим второе число, метод hasNext снова проверит, может ли он вернуть больше значений. Он уже вернул два значения, но теперь список содержит только одно. Но вот код:

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

1 != 2, поэтому мы переходим к методу next(), который теперь понимает, что кто-то возился со списком, и запускает исключение.

Надеюсь, это прояснит ваш вопрос.

Резюме

List.remove() не будет выдавать ConcurrentModificationException при удалении предпоследнего элемента из списка.

person pushy    schedule 18.11.2011
comment
@pushy: Только ответ, который, кажется, отвечает на то, что на самом деле задает вопрос, и объяснение хорошее. Я принимаю этот ответ, а также +1. Спасибо. - person Bhesh Gurung; 21.11.2011

Один из способов справиться с этим — удалить что-то из копии 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);
    }
}
person James Raitsev    schedule 18.11.2011

Метод пересылки/итератора не работает при удалении элементов. Вы можете удалить элемент без ошибки, но при попытке доступа к удаленным элементам вы получите ошибку времени выполнения. Вы не можете использовать итератор, потому что, как показывает настойчивость, он вызовет исключение 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);
    }
}
person RightHandedMonkey    schedule 11.09.2013
comment
гениальная идея ! - person dobrivoje; 28.05.2020

Этот фрагмент всегда выдает исключение ConcurrentModificationException.

Правило таково: «Вы не можете изменять (добавлять или удалять элементы из списка) во время итерации по нему с помощью итератора (что происходит, когда вы используете цикл for-each)».

JavaDocs:

Итераторы, возвращаемые методами iterator и listIterator этого класса, являются отказоустойчивыми: если список структурно изменен в любое время после создания итератора любым способом, кроме как с помощью собственных методов удаления или добавления итератора, итератор выдаст исключение ConcurrentModificationException.

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

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

person Bhushan    schedule 18.11.2011
comment
В ОП четко указано, что один из циклов НЕ генерирует исключение, и вопрос был в том, почему это произошло. - person madth3; 19.11.2011
comment
что вы имеете в виду под "допрашивающим"? - person Bhushan; 19.11.2011

У меня была такая же проблема, но в случае, если я добавлял элемент 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() никогда не обманет вас, потому что, когда вы удаляете/добавляете что-то в список, размер списка уменьшается/увеличивается..

Надеюсь, это поможет, для меня это было решением.

person Gondil    schedule 11.05.2014
comment
Это неправда ! Доказательство: запустите этот фрагмент, чтобы увидеть результат: 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++) { if (listOfBooks.get(index).contains(22)) { listOfBooks.remove(index); } } System.err.println(После удаления: + listOfBooks); } - person dobrivoje; 28.05.2020

Если вы используете коллекции с копированием при записи, это сработает; однако, когда вы используете list.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);
            }
        }
    }
}
person JohnnyO    schedule 24.02.2015

Это отлично работает на Java 1.6

~ % javac RemoveListElementDemo.java
~ % java RemoveListElementDemo
~ % cat 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);
            }
        }
    }
}

~ %

person battosai    schedule 12.07.2012
comment
Извините за опечатку. Это нормально работает на Java 1.6. - person battosai; 12.07.2012
comment
Хм... Может у вас другая реализация. Но по спецификации так и должно быть, ИМО. Посмотрите на ответ @Pushy. - person Bhesh Gurung; 13.07.2012
comment
к сожалению, id не работает в java 1.8 - person dobrivoje; 28.05.2020

В моем случае я сделал это так:

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

Измените итератор for each на for loop для решения.

И Причина:

Итераторы, возвращаемые методами iterator и listIterator этого класса, являются отказоустойчивыми: если список структурно изменен в любое время после создания итератора любым способом, кроме как с помощью собственных методов удаления или добавления итератора, итератор выдаст исключение ConcurrentModificationException.

--Рекомендуемая документация по Java.

person Stephen    schedule 07.05.2018

Проверьте свой код, чувак....

В основном методе вы пытаетесь удалить 4-й элемент, которого нет, и, следовательно, ошибка. В методе remove() вы пытаетесь удалить третий элемент, который есть, и, следовательно, нет ошибки.

person Abhishek    schedule 22.08.2013
comment
Вы ошибаетесь: числа 2 и 3 являются не индексами для списка, а элементами. Обе логики удаления проверяют equals по элементам списка, а не по индексу элементов. Кроме того, если бы это было связано с индексом, это было бы IndexOutOfBoundsException, а не ConcurrentModificationException. - person Malte Hartwig; 05.02.2018