Java.util.ConcurrentModificationException не выбрасывается, когда ожидается
Следующий код генерирует java.util.ConcurrentModificationException, как и ожидалось:
public void test(){
ArrayList<String> myList = new ArrayList<String>();
myList.add("String 1");
myList.add("String 2");
myList.add("String 3");
myList.add("String 4");
myList.add("String 5");
for(String s : myList){
if (s.equals("String 2")){
myList.remove(s);
}
}
}
Однако следующий код не бросает исключение, в то время как я ожидаю, что оно будет брошено:
public void test(){
ArrayList<String> myList = new ArrayList<String>();
myList.add("String 1");
myList.add("String 2");
myList.add("String 3");
for(String s : myList){
if (s.equals("String 2")){
myList.remove(s);
}
}
}
Разница в том, что первый список содержит 5 элементов, а второй список содержит 3. Используемый JVM:
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
Вопрос: почему вторая часть кода НЕ бросает java.util.ConcurrentModificationException?
Ответы
Ответ 1
Итератор, возвращенный из ArrayList.iterator()
в реализации, мы, по-видимому, используем только проверки структурной модификации при вызовах next()
, а не в вызовах hasNext()
. Последний выглядит так (под Java 8):
public boolean hasNext() {
return cursor != size;
}
Итак, во втором случае итератор "знает", что он возвратил два элемента, и что список содержит только два элемента... так что hasNext()
просто возвращает false, и мы никогда не будем называть next()
третьим время.
Я бы рассматривал это как деталь реализации - в основном проверка не была настолько строгой, насколько это могло бы быть. Было бы вполне разумно, если бы hasNext()
выполнил проверку и выдал исключение в этом случае тоже.
Ответ 2
Обратите внимание также на последний абзац краткое описание документации ArrayList:
Неудачные итераторы бросают ConcurrentModificationException
на наилучшим образом. Поэтому было бы неправильно писать программу что зависело от этого исключения за его правильность: неудачный поведение итераторов должно использоваться только для обнаружения ошибок.
Если вы беспокоитесь о том, что списки принудительного доступа должны быть доступны только для чтения, используйте метод Collections.unmodifiableList
вместо проверки на ConcurrentModificationException, который, как указано выше, не гарантируется для всех соответствующих случаев.