Подтвердить что ты не робот

ConcurrentModificationException при добавлении внутри цикла foreach в ArrayList

Я пытаюсь использовать цикл foreach с arraylist, но когда я его использую, он дает мне ошибку, но когда я использую обычный цикл, он отлично работает, что может быть проблемой?

Код находится здесь:

for (Pair p2 : R) {
    if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), p2.getSecondElm()));
    else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p2.getFirstElm(), p.getSecondElm()));

    // else
    // There are no transitive pairs in R.
}

это цикл, который не работает, и вот тот, который работает:

for (int i = 0; i < R.size(); i++) {
    if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), R.get(i).getSecondElm()));
    else if ((R.get(i).getSecondElm() == p.getFirstElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (R.get(i).getFirstElm(), p.getSecondElm()));
    //else
    //  There are no transitive pairs in R.
}

ошибка, которую я получаю при использовании цикла foreach:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)  
    at set.problem.fourth.PoSet.makeTransitive(PoSet.java:145)  
    at set.problem.fourth.PoSet.addToR(PoSet.java:87)
    at set.problem.fourth.PoSetDriver.typicalTesting(PoSetDriver.java:35)
    at set.problem.fourth.PoSetDriver.main(PoSetDriver.java:13)
4b9b3361

Ответ 1

Классы Java Collection неудачны, что означает, что если Коллекция будет изменена, пока какой-то поток пересекает ее используя итератор, iterator.next() будет бросать ConcurrentModificationException.

Эта ситуация может возникнуть в случае многопоточных, а также одиночных с резьбой. - www.javacodegeeks.com

Вы не можете изменить List в цикле for/each, который является синтаксическим сахаром вокруг Iterator в качестве детали реализации. Вы можете безопасно звонить .remove() только при непосредственном использовании Iterator.

Обратите внимание, что Iterator.remove является единственным безопасным способом изменения коллекции во время итерации; поведение неуказано, если основной коллекция модифицируется любым другим способом, пока итерация прогресс. - Коллекции Java Учебник

Вызов .add() внутри цикла for/each изменяет содержимое, а Iterator, который используется за кулисами, видит это и выдает это исключение.

Более тонкая проблема заключается в том, что второй способ, который вы перечисляете, .size() увеличивается каждый раз, когда вы .add(), так что вы закончите обработку всех вещей, которые вы .add(), это может привести к бесконечному циклу на каких входных данных. Я не уверен, что это то, чего вы желаете.

Решение

Я бы создал еще ArrayList и .add() для всех новых вещей, а затем после цикла использовал .addAll() в исходном ArrayList, чтобы объединить два списка вместе. Это сделает вещи явными в том, что вы пытаетесь сделать, то есть, если ваше намерение не обрабатывает все добавленные вами вещи, когда вы их добавляете.

2014 Решение:

Всегда используйте классы коллекций Immutable и создавайте новые классы коллекций Immutable вместо того, чтобы пытаться изменить один общий. Это в основном то, что говорит мой ответ 2012 года, но я хотел сделать его более явным.

Guava поддерживает это очень хорошо, используйте ImmutableList.copyOf() для передачи данных.

Использовать Iterables.filter(), чтобы отфильтровать материал в новом ImmutableList, без общего измененного состояния, означает отсутствие проблем concurrency!

Ответ 2

Под капотом для каждого цикла в Java используется Iterator для перемещения коллекции (см. статью для подробного объяснение.) И Итератор будет бросать ConcurrentModificationException, если вы измените коллекцию, итерации по ней, см. этот пост.

Ответ 3

Проблема в том, что вы делаете R.add() в первой строке цикла.

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

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

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