Java.util.ConcurrentModificationException с итератором
Я знаю, если бы попытался удалить из цикла коллекции через него с помощью простого цикла, я получаю это исключение: java.util.ConcurrentModificationException
. Но я использую Iterator, и он все еще генерирует мне это исключение. Любая идея, почему и как ее решить?
HashSet<TableRecord> tableRecords = new HashSet<>();
...
for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
TableRecord record = iterator.next();
if (record.getDependency() == null) {
for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
iterator.remove();
}
}
}
}
Ответы
Ответ 1
Вы должны использовать iterator.remove()
вместо tableRecords.remove()
Вы можете удалить элементы в списке, на котором вы выполняете итерацию, только если вы используете метод remove из итератора.
ИЗМЕНИТЬ:
Когда вы создаете итератор, он начинает подсчитывать изменения, которые были применены к коллекции. Если итератор обнаруживает, что некоторые изменения были сделаны без использования его метода (или с использованием другого итератора в том же сборнике), он больше не может гарантировать, что он не пройдет дважды на одном и том же элементе или пропустит один, поэтому он выбрасывает это исключение
Это означает, что вам нужно изменить свой код, чтобы вы удаляли только элементы через iterator.remove(и только с одним итератором).
ИЛИ
создайте список элементов для удаления, затем удалите их после завершения итерации.
Ответ 2
Итератор с ошибкой проверяет свойства для любой модификации в структуру базовой коллекции каждый раз, когда мы пытаемся получить следующий элемент. Если есть какие-либо изменения, они бросают ConcurrentModificationException. Все реализации Итератора в классах коллекций отказоустойчивы по дизайну, за исключением параллельных таких как ConcurrentHashMap и CopyOnWriteArrayList.
Источник: Google
Вы лучше поймете пример, приведенный ниже: -
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class IteratorExp {
public static void main(String... q) {
//CASE - ONE
List<String> strList = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> itr = strList.iterator();
/*
* strList.add("e"); strList.add("f"); strList.add("g");
*/
while (itr.hasNext()) {
System.out.println(itr.next());
}
/*
* Exception in thread "main" java.util.ConcurrentModificationException
* at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at
* java.util.ArrayList$Itr.next(Unknown Source) at
* IteratorExp.main(IteratorExp.java:14)
*/
//CASE - TWO
List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
Iterator<Integer> itrOne = intList.iterator();
Iterator<Integer> itrTwo = intList.iterator();
for (; itrOne.hasNext();) {
if (itrOne.next().equals(5)) {
itrOne.remove(); // #1
//intList.remove(itrOne.next()); // #2
}
}
for (; itrTwo.hasNext();) {
if (itrTwo.next().equals(5)) {
itrTwo.remove(); // #1
//intList.remove(itrTwo.next()); // #2
}
}
/*
* Exception in thread "main" java.util.ConcurrentModificationException
* at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at
* java.util.ArrayList$Itr.next(Unknown Source) at
* IteratorExp.main(IteratorExp.java:35)
*/
}
}
Ответ 3
Контракт для итератора HashSet заключается в том, что вы не можете удалить из hashset, кроме как через этот метод удаления удаленных итераторов. С точки зрения dependencyIt
вы удалили элемент, отличный от вызова его метода remove
, чтобы он выбрал ConcurrentModificationException
.
Кажется, вы хотите удалить записи из своего хэшета, если у них одинаковый идентификатор записи. Не было бы проще переопределить ваши методы записи equals
и hashcode
, чтобы гарантировать, что записи с одинаковым идентификатором равны и имеют один и тот же хэш-код? (если это имеет смысл, конечно)
Ответ 4
Проблема в том, что у вас одновременно есть два итератора, и они "сражаются" друг с другом. Самый простой способ исправить проблему - просто вырваться из внутреннего цикла, если вы найдете совпадение:
for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
TableRecord record = iterator.next();
if (record.getDependency() == null) {
for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
iterator.remove();
break; // ADD THIS LINE
}
}
}
}
Java Iterator
предназначены для "быстрого сбоя", когда их базовый контейнер изменяется без изменения с помощью Iterator
. Вы используете вложенные итераторы, поэтому любая операция remove()
, выдаваемая одному, заставит другого выбросить Exception
, если он будет использоваться. По этой причине, если вам нужно выпустить remove()
, вам нужно будет сделать это на "внешнем" итераторе (который вы делаете) и прекратите использование второго итератора впоследствии (который добавил break
оператор делает).