Подтвердить что ты не робот

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?

4b9b3361

Ответ 1

Итератор, возвращенный из ArrayList.iterator() в реализации, мы, по-видимому, используем только проверки структурной модификации при вызовах next(), а не в вызовах hasNext(). Последний выглядит так (под Java 8):

public boolean hasNext() {
    return cursor != size;
}

Итак, во втором случае итератор "знает", что он возвратил два элемента, и что список содержит только два элемента... так что hasNext() просто возвращает false, и мы никогда не будем называть next() третьим время.

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

Ответ 2

Обратите внимание также на последний абзац краткое описание документации ArrayList:

Неудачные итераторы бросают ConcurrentModificationException на наилучшим образом. Поэтому было бы неправильно писать программу что зависело от этого исключения за его правильность: неудачный поведение итераторов должно использоваться только для обнаружения ошибок.

Если вы беспокоитесь о том, что списки принудительного доступа должны быть доступны только для чтения, используйте метод Collections.unmodifiableList вместо проверки на ConcurrentModificationException, который, как указано выше, не гарантируется для всех соответствующих случаев.