Почему я не получаю исключение java.util.ConcurrentModificationException в этом примере? - PullRequest
172 голосов
/ 19 ноября 2011

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

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

Ответы [ 10 ]

257 голосов
/ 19 ноября 2011

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

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

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

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

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

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

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

Краткое описание

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

42 голосов
/ 19 ноября 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);
    }
}
13 голосов
/ 11 сентября 2013

Метод 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);
    }
}
6 голосов
/ 19 ноября 2011

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

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

JavaDocs:

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

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

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

5 голосов
/ 11 мая 2014

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

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

1 голос
/ 07 мая 2018

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

И причина:

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

- ссылки на документы Java.

1 голос
/ 24 февраля 2015

Если вы используете коллекции копирования при записи, это будет работать;однако когда вы используете 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);
            }
        }
    }
}
1 голос
/ 12 июля 2012

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

~%

0 голосов
/ 05 ноября 2014

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

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
0 голосов
/ 22 августа 2013

Проверьте ваш код man ....

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

...