Чому я не отримую 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а інший не.
Bhesh Gurung

Існує різниця в елементі, який ви видаляєте. У методі вилучіть "середній елемент". В основному ви видаляєте останню. Якщо ви поміняєте місцями, ви отримаєте виняток у своєму методі. Досі не впевнений, чому це все-таки.
Бен ван Гомпель

У мене була подібна проблема, коли мій цикл повторював також положення, яке не існувало після того, як я видалив елемент у циклі. Я просто виправив це, додавши return;в цикл.
франк17

на java8 Android, видалення іншого елемента, ніж останній, викликало б ConcurrentModificationException. тож у вашому випадку функція видалення отримає виняток, протилежний тому, який ви спостерігали раніше.
gonglong

Відповіді:


262

Ось чому: Як сказано в Javadoc:

Ітератори, повернені ітератором цього класу та методом listIterator цього класу, невдалі: якщо список структурно модифікований в будь-який час після створення ітератора, будь-яким способом, крім методу власного видалення або додавання ітератора, ітератор викине ConcurrentModificationException.

Ця перевірка робиться в next()методі ітератора (як ви бачите по стек-трасі). Але ми дістанемося next()методу лише в тому випадку hasNext(), якщо він виконаний істинним, що викликається тим, щоб кожен перевірив, чи відповідає межа. У вашому методі видалення, коли hasNext()перевіряє, чи потрібно йому повернути інший елемент, він побачить, що він повернув два елементи, і тепер після видалення одного елемента список містить лише два елементи. Таким чином, все персикове, і ми робимо з ітерацією. Перевірка на одночасні модифікації не відбувається, оскільки це робиться в next()методі, який ніколи не викликається.

Далі переходимо до другої петлі. Після того як ми видалимо друге число, метод hasNext перевірить, чи зможе повернути більше значень. Він уже повернув два значення, але тепер список містить лише одне. Але тут код:

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

1! = 2, тому ми продовжуємо next()метод, який тепер усвідомлює, що хтось зіпсував цей список і запускає виняток.

Сподіваємось, що це вирішить ваше запитання.

Підсумок

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


5
@pushy: Тільки відповідь, яка, здається, відповідає на те, що насправді задають питання, і пояснення добре. Я приймаю цю відповідь, а також +1. Дякую.
Bhesh Gurung

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

При видаленні елементів метод пересилання / ітератор не працює. Ви можете видалити елемент без помилок, але при спробі отримати доступ до видалених елементів ви отримаєте помилку виконання. Ви не можете використовувати ітератор, оскільки, як наполегливі покази, це спричинить ConcurrentModificationException, тому використовуйте замість нього звичайний цикл, але пройдіться назад через нього.

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

геніальна ідея!
dobrivoje

7

Цей фрагмент завжди буде кидати ConcurrentModificationException.

Правило таке: "Ви не можете змінювати (додавати або видаляти елементи зі списку) під час ітерації над ним за допомогою ітератора (що відбувається, коли ви використовуєте цикл для кожного)".

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

Сподіваюся, це допоможе, для мене це було рішення.


Це не правда ! Докази: запустіть цей фрагмент, щоб побачити результат: public static void main (String ... args) {Список <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); }
dobrivoje

1

Якщо ви користуєтеся колекціями, що копіюються, то це буде працювати; однак, коли ви використовуєте list.iterator (), повернутий Iterator завжди посилатиметься на колекцію елементів, як це було, коли (як показано нижче) list.iterator () викликався, навіть якщо інший потік модифікує колекцію. Будь-які способи мутації, викликані в 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);
            }
        }
    }
}

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

~%


Вибачте за друкарські помилки Це "працює" добре на Java 1.6
battosai

Хм ... Може, у вас інша реалізація. Але згідно специфікації, це повинно це робити, ІМО. Подивіться на відповідь @ Pushy.
Bhesh Gurung

на жаль, id не на java 1,8
dobrivoje

0

У моєму випадку я зробив це так:

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

0

Зміна ітератора for eachв for loopвирішити.

Причина:

Ітератори, повернені ітератором цього класу та методом listIterator цього класу, невдалі: якщо список структурно модифікований в будь-який час після створення ітератора, будь-яким способом, крім методу власного видалення або додавання ітератора, ітератор викине ConcurrentModificationException.

- Згадані документи Java.


-1

Перевірте свій код людини ....

У головному методі ви намагаєтесь видалити четвертий елемент, якого немає, а значить, помилка. У методі delete () ви намагаєтесь видалити 3-й елемент, який є, і, отже, немає помилок.


Ви помиляєтесь: цифри 2і 3не індекси для списку, а елементи. Обидві логіки видалення перевіряють наявність equalsелементів списку, а не індексу елементів. Крім того, якби це було пов'язано з індексом, було б IndexOutOfBoundsException, ні ConcurrentModificationException.
Malte Hartwig
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.