Итерация по списку (ConcurrentModificationException)

Следующий код вызывает исключение ConcurrentModificationException:

for (String word : choices) {
         List<String> choicesCopy = choices;
         chosen.add(word);
         choicesCopy.remove(word);
         subsets(choicesCopy, chosen, alreadyPrinted);
}

В чем дело? Исходный список (выборы) вообще не изменился.


person user658168    schedule 27.05.2011    source источник
comment
это точная копия stackoverflow.com/questions /6146690/ от того же пользователя. Другой на самом деле, имеет больше деталей.   -  person Alex Gitelman    schedule 27.05.2011


Ответы (6)


Вы сделали здесь ссылочную копию, а не копию объекта

List<String> choicesCopy = choices;

Таким образом, очевидно, что вы изменяете один и тот же список и обязательно получите ConcurrentModificationException

Используйте ссылку Collections.copy(), чтобы правильно сделать копию списка.

EDIT: Как предложено ниже, вы также можете использовать конструктор для копирования.

person Manoj    schedule 27.05.2011
comment
Использование конструктора для копирования (например, new ArrayList<String>(choices)) предпочтительнее использования Collections.copy. - person ColinD; 27.05.2011
comment
Альтернативным (лучшим?) способом копирования будет List<String> choicesCopy = new ArrayList<String>(choices). Ваше предложение будет включать в себя создание пустого списка для передачи в метод copy(), и кажется более разумным просто использовать конструктор копирования в этой точке (меньше кода, более чистое намерение и более эффективное распределение в конструкторе списка). - person Andrzej Doyle; 27.05.2011

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

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

person RMT    schedule 27.05.2011

Измените код следующим образом:

for (Iterator<String> it = choices.iterator(); it.hasnext();) {
     String word = it.next();
     chosen.add(word);
     it.remove();
     subsets(choicesCopy, chosen, alreadyPrinted);
}

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

Во время итерации любые другие способы удаления данных из коллекции приведут к ошибке ConcurrentModificationException.

person Sean Patrick Floyd    schedule 27.05.2011

Я думаю, что универсальное решение:

List<E> obj = Collections.synchronizedList(new ArrayList<E>());
person klawx3    schedule 16.12.2011
comment
Нет, это не поможет. Я бы понизил его, если бы у меня был представитель. Все это синхронизирует чтение и запись. Он не изменяет итератор для обработки изменений. Однако есть определенные структуры данных, которые обрабатывают это (только одна, которая выглядит правильно, я могу найти прямо сейчас, это ConcurrentHashMap и ConcurrentLinked(De)Queue) - person foozbar; 26.05.2014

Вам нужно будет правильно скопировать список, например. Collections.copy, а затем удалите из копии или используйте Iterator.remove, который удалит объект из базовой коллекции. Итераторы быстро выходят из строя, поэтому вы не можете изменить базовую коллекцию, не используя API итератора.

person planetjones    schedule 27.05.2011

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

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

Возможно код должен быть.

Set<String> choices =
Set<String> chosen =
for (String word : choices) {
     Set<String> choicesCopy = new LinkedHashSet<String>(choices);
     choicesCopy.remove(word);
     Set<String> chosenCopy = new LinkedHashSet<String>(chosen);
     chosenCopy.add(word);

     subsets(choicesCopy, chosenCopy, alreadyPrinted);
}
person Peter Lawrey    schedule 27.05.2011