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

176

Примечание: я знаю о 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);
            }
        }
    }
}
Беш Гурунг
источник
3
Единственный безопасный способ удалить элемент из списка во время итерации по этому списку - использовать Iterator#remove(). Почему ты так делаешь?
Мэтт Болл
@MattBall: я просто пытался понять, в чем причина. Потому что это то же самое «расширенное для цикла» в обоих методах, но один бросает, ConcurrentModificationExceptionа другой нет.
Беш Гурунг
Существует различие в элементе, который вы удаляете. В методе вы удалите «средний элемент». В основном вы удаляете последнее. Если вы поменяете местами, вы получите исключение в вашем методе. Все еще не уверен, почему это все же.
Бен ван Гомпель
У меня была похожая проблема, когда мой цикл повторял также положение, которого не было после того, как я удалил элемент в цикле. Я просто исправил это, добавив return;в цикл.
frank17
на java8 Android удаление элемента, отличного от последнего, вызовет исключение ConcurrentModificationException. так что в вашем случае, функция удаления получит исключение, которое противоположно тому, что вы наблюдали ранее.
Гонглонг

Ответы:

262

И вот почему: как говорится в Javadoc:

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

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

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

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

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

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

Резюме

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

напористый
источник
5
@pushy: Только ответ, который, кажется, отвечает тому, что на самом деле задает вопрос, и объяснение хорошее. Я принимаю этот ответ, а также +1. Спасибо.
Беш Гурунг
42

Один из способов справиться с этим - удалить что-либо из копии 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);
    }
}
Джеймс Райцев
источник
14

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

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

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

JavaDocs:

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

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

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

Бхушан
источник
3
ОП четко заявляет, что один из циклов НЕ выдает исключение, и опрашивающий выяснил, почему это произошло.
Madth3
что вы подразумеваете под «вопросительный»?
Бхушан
4

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

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

Gondil
источник
Это неправда ! Подтверждение: запустите этот фрагмент, чтобы увидеть результат: 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); }
Добривое
1

Если вы используете коллекции копирования при записи, это будет работать; однако, когда вы используете list.iterator (), возвращаемый Iterator всегда будет ссылаться на коллекцию элементов, как это было, когда (как показано ниже) вызывался list.iterator (), даже если другой поток изменяет коллекцию. Любые мутирующие методы, вызываемые для итератора на основе копирования при записи или ListIterator (например, для добавления, установки или удаления), будут генерировать исключение 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);
            }
        }
    }
}
JohnnyO
источник
0

Это нормально работает на 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);
            }
        }
    }
}

~%

Battosai
источник
Извините за опечатку Это прекрасно работает на Java 1.6
battosai
Хм ... Может быть, у вас есть другая реализация. Но согласно спецификации это должно быть сделано, IMO. Посмотрите на ответ @ Pushy.
Беш Гурунг
к сожалению, идентификатор не на Java 1.8
dobrivoje
0

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

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
Саиф Хамед
источник
0

Замените Iterator for eachна, for loopчтобы решить.

И причина в том, что:

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

- Ссылка на Java Документы.

Стивен
источник
-1

Проверьте ваш код, человек ....

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

Абхишек
источник
Вы ошибаетесь: цифры 2и 3не индексы для списка, а элементы. Обе логики удаления проверяют на equalsсоответствие элементам списка, а не индексу элементов. Кроме того, если бы это было связано с индексом, это IndexOutOfBoundsExceptionне так ConcurrentModificationException.
Малте Хартвиг