Я полагаю, что многие студенты столкнулись со следующим исключением при работе со списком:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
Без лишних слов, вот несколько примеров, чтобы проиллюстрировать проблему и проанализировать ее причины.
Пример 1
package main.java.mo.basic;
import java.util.ArrayList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
- Результаты
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at main.java.mo.basic.ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:17)
- Проанализируйте причины
Во-первых, мы знаем, что принцип реализации расширенного цикла for — это очень важный интерфейс Iterator. Только тогда мы можем проанализировать, почему есть исключения, и этот пункт знания также является самым важным и основным.
В соответствии с приведенной выше информацией об исключении видно, что исключение из «для (String string: strings) {», эта строка выдает из, как эта линия может пойти не так? Поняв принцип реализации расширенного for, мы будем знать, что при выполнении этой строки кода Будут вызываться два метода класса реализации Iterator, hasNext() и next(), поэтому эта точка знаний является наиболее важной и основной. из.
Сначала посмотрите на часть исходного кода ArrayList.Iterator и часть исходного кода ArrayList.remove(Object o)
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
Мы обнаружим, что при выполнении метода remove(Object o) размер объекта ArrayList уменьшается на единицу и size==4, modCount++, затем cursor==5 в объекте Iterator, hasNext возвращает true, что приводит к расширенному циклу for Цикл переходит к поиску следующего элемента и вызывает метод next().Когда checkForCommodification выполняет проверку, он находит modCount Это не соответствует ожидаемому значению ModCount в объекте Iterator, что указывает на то, что объект ArrayList был изменен. Для предотвращения ошибок генерируется исключение ConcurrentModificationException.
Возвращаясь назад и снова размышляя о коде ArrayList, давайте взглянем на сам ArrayList и внутренние классы Itr, Itr реализует Iterator, чтобы вернуться к ArrayList.iterator(), при использовании можно сказать, что они Два независимых класса, каждый из которых имеет два важных свойства: size, modCount в ArrayList и в Itr. курсор, ожидаемыйModCount, теоретически они синхронны, но некоторые операции мы будем вызывать в процессе вызывания Они несовместимы.Например, в этом примере мы вызываем метод ArrayList.remove(), изменяя размер и Атрибут modCount, но курсор и ожидаемыйModCount в Itr не изменились. Когда цикл выполняется снова, вызывается метод из Itr, и, наконец, обнаруживается, что данные несовместимы. Это случай ConcurrentModificationException основная причина.
Теперь, когда мы четко проанализировали проблему, как ее решить? Здесь мы отступаем от этой мысли и перечисляем централизованное решение.
-
Решать проблему
- Не используйте расширенный цикл for
Для этого примера ясно, что мы знаем, что исключение вызвано свойствами в ArrayList и свойствами во внутреннем классе Itr. Если атрибуты несовместимы, можно предположить, что класс Itr не спроектирован, если цикл for и операция удаления не разработаны. Да, идея очень ясна, это так просто. Код сначала ничего не говоря.
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); for (int i = 0; i < strings.size(); i++) { String element = strings.get(i); if("e".equals(element)){ strings.remove(element); i --;//需要自己手动维护索引 } }
Использование этого метода для обработки операции удаления смущает тем, что вам нужно вручную поддерживать индекс самостоятельно, чтобы избежать потери данных.
- Используйте метод удаления в итераторе, не связывайтесь с методом удаления в ArrayList
Основываясь на приведенных выше идеях, поскольку я не хочу смотреть на Itr, похоже, он использует метод удаления непосредственно в классе Itr. Не было бы хорошей идеей использовать Itr для перебора объектов. код выше.
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); Iterator<String> iterator = strings.iterator(); while (iterator.hasNext()){ String element = iterator.next(); if("e".equals(element)){ iterator.remove(); } }
- При удалении элементов уже не обходит, просто removeAll Поскольку исключение возникает, когда список просматривается и удаляется, хорошо, насилие, могу ли я выполнить операцию удаления без обхода? Что ж, идея правильная, она вас удовлетворит.
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); ArrayList<String> tempStrings = new ArrayList<String>(); for (String string : strings) { if("e".equals(string)){ tempStrings.add(string); } } strings.removeAll(tempStrings);
- другие методы Всегда есть много идей, таких как добавление блокировки, чтобы убедиться, что данные верны, и ее удаление, чтобы убедиться, что вы реализуете ArrayList, Вы можете играть во что хотите.Если это удобно, используйте CopyOnWriteArrayList непосредственно в пакете java.util.concurrent. Есть много способов быть счастливым.
Пример 2
После разговора о примере 1 и примере 2 это был просто ArrayList, теперь попробуйте LinkedList.
package main.java.mo.basic;
import java.util.LinkedList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
LinkedList<String> strings = new LinkedList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
Этот код ничем не отличается от Примера 1. Разница лишь в том, что ArrayList заменен на LinkedList, и вдруг я обнаруживаю, что выполнение этого кода не сообщает об ошибке. Разве это не то, что нужно сделать? Ну и еще один кусок кода.
package main.java.mo.basic;
import java.util.LinkedList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
LinkedList<String> strings = new LinkedList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
strings.add("f");
strings.add("g");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
Выполните этот фрагмент кода еще раз, и возвращенный результат будет таким:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:953)
at java.util.LinkedList$ListItr.next(LinkedList.java:886)
at main.java.mo.basic.ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:19)
Присмотревшись, я обнаружил, что в строках есть еще два элемента, почему такая большая разница, метод анализа точно такой же, как в примере 1. Должно быть очень просто найти ответ по анализу Примера 1, поэтому примера нет.
Суммировать
В целом, хотя в этой статье не рассматривается возникновение ConcurrentModificationException подробно, Но методы понимания и идеи те же. Два примера в статье говорят нам, Когда операция удаления элемента выполняется в классе реализации, обрабатывающем Iterable, и обрабатывается в цикле for, Понимание этих вещей позволит избежать багов и ошибок.