Почему этот код не вызывает исключение ConcurrentModificationException?
Я читал о ConcurrentModificationException и как его избежать. Найдена статья . Первый список в этой статье имел код, похожий на следующий, что, по-видимому, вызывало бы исключение:
List myList = new ArrayList(); myList.add("January"); myList.add("February"); myList.add("March"); Iterator it = myList.iterator(); while(it.hasNext()) { String item = it.next(); if("February".equals(item)) { myList.remove(item); } } for (String item : myList) { System.out.println(item); }
Затем он продолжил объяснять, как решить проблему с различными предложениями.
Когда я попытался воспроизвести его, я не получил исключения! Почему я не получаю исключения?
- Файл Write - PrintStream append
- Которая @NonNull Java-аннотация для использования
- Доступ Java JDBC Access для пользователя
- разность fn (String ... args) vs fn (String args)
- Java 8 Iterable.forEach () vs foreach loop
- Почему этот общий метод с привязкой возвращает какой-либо тип?
- Как создать файл FXML для уже созданного нового компонента в java, чем добавить его в конструктор сцен?
- Самый простой способ объединить два списка в карту (Java)?
- Многочисленные сканеры Java
- Почему при вызове (статического) метода в нулевой ссылке не выбрасывается исключение NullPointerException?
- Невозможно десериализовать lambda
- Шаблоны проектирования: Factory vs Factory method vs Abstract Factory
- Добавление строк в JTable
В соответствии с документами API Java API Iterator.hasNext не генерирует исключение ConcurrentModificationException
.
После проверки "January"
и "February"
вы удаляете один элемент из списка. Вызов it.hasNext()
не it.hasNext()
ConcurrentModificationException
но возвращает false. Таким образом, ваш код выходит из строя. Однако последняя строка никогда не проверяется. Если вы добавите "April"
в список, вы получите Exception, как ожидалось.
import java.util.List; import java.util.ArrayList; import java.util.Iterator; public class Main { public static void main(String args[]) { List
вmyList = new ArrayList (); myList.add("January"); myList.add("February"); myList.add("March"); myList.add("April"); Iterator it = myList.iterator(); while(it.hasNext()) { String item = it.next(); System.out.println("Checking: " + item); if("February".equals(item)) { myList.remove(item); } } for (String item : myList) { System.out.println(item); } } } import java.util.List; import java.util.ArrayList; import java.util.Iterator; public class Main { public static void main(String args[]) { List
myList = new ArrayList (); myList.add("January"); myList.add("February"); myList.add("March"); myList.add("April"); Iterator it = myList.iterator(); while(it.hasNext()) { String item = it.next(); System.out.println("Checking: " + item); if("February".equals(item)) { myList.remove(item); } } for (String item : myList) { System.out.println(item); } } }
Из источника ArrayList
(JDK 1.7):
private class Itr implements Iterator { 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]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
Каждая операция модификации на ArrayList
увеличивает поле modCount
(количество раз, когда список был изменен после создания).
Когда создается iterator, он сохраняет текущее значение modCount
в expectedModCount
. Логика такова:
- если список не изменяется вообще во время итерации,
modCount == expectedModCount
- если список изменен собственным методом
remove()
, тоmodCount
увеличивается, ноexpectedModCount
что значениеModCount также увеличивается, поэтомуmodCount == expectedModCount
прежнему сохраняется - если какой-либо другой метод (или даже какой-то другой экземпляр iteratorа) изменяет список,
modCount
получает incremented, поэтомуmodCount != expectedModCount
, что приводит кConcurrentModificationException
Однако, как вы можете видеть из источника, проверка не выполняется в hasNext()
, только в next()
. Метод hasNext()
также сравнивает только текущий индекс с размером списка. Когда вы удалили второй из последних элементов из списка ( "February"
), это привело к тому, что следующий вызов hasNext()
просто вернул false
и завершил итерацию до того, как CME мог быть выброшен.
Однако, если вы удалили какой-либо элемент, отличный от второго, последнее исключение было бы выбрано.
Я думаю, что правильное объяснение – это выдержка из javadocs ConcurrentModificationExcetion:
Обратите внимание на то, что отказоустойчивое поведение не может быть гарантировано, поскольку, как правило, невозможно сделать какие-либо серьезные гарантии при наличии несинхронизированной параллельной модификации. Неудачные операции бросают ConcurrentModificationException с максимальной эффективностью. Поэтому было бы неправильно писать программу, зависящую от этого исключения, за ее правильность: ConcurrentModificationException следует использовать только для обнаружения ошибок.
Итак, если iterator не работает быстро, он может исключить исключение, но нет никакой гарантии. Попробуйте заменить February
на January
в вашем примере и исключение будет выбрано (по крайней мере, в моей среде)
Итератор проверяет, что он повторил столько раз, сколько осталось у вас, чтобы увидеть, что он достиг цели, прежде чем он проверит параллельную модификацию. Это означает, что если вы удалите только второй последний элемент, вы не увидите CME в том же iteratorе.