Por que esse código não está causando um ConcurrentModificationException?

Eu estava lendo sobre ConcurrentModificationException e como evitá-lo. Encontrei um artigo . A primeira listview nesse artigo tinha código semelhante ao seguinte, que aparentemente causaria a exceção:

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); } 

Em seguida, passou a explicar como resolver o problema com várias sugestões.

Quando tentei reproduzi-lo, não consegui a exceção! Por que não estou recebendo a exceção?

De acordo com os documentos da API Java, o Iterator.hasNext não lança uma ConcurrentModificationException .

Depois de verificar "January" e "February" você remove um elemento da lista. Chamar it.hasNext() não lança um ConcurrentModificationException mas retorna false. Assim, o seu código sai limpo. A última string, no entanto, nunca é verificada. Se você adicionar "April" à lista, receberá a exceção como esperado.

 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); } } } 

http://ideone.com/VKhHWN

Da fonte 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(); } } 

Cada operação de modificação em um ArrayList incrementa o campo modCount (o número de vezes que a lista foi modificada desde a criação).

Quando um iterador é criado, ele armazena o valor atual de modCount em expectedModCount . A lógica é:

  • se a lista não for modificada durante a iteração, modCount == expectedModCount
  • se a lista é modificada pelo próprio método remove() do iterador, modCount é incrementado, mas expectedModCount é incrementado, assim modCount == expectedModCount ainda é modCount == expectedModCount
  • se algum outro método (ou mesmo alguma outra instância do iterador) modificar a lista, modCount será incrementado, portanto modCount != expectedModCount , que resulta em ConcurrentModificationException

No entanto, como você pode ver na origem, a verificação não é executada no método hasNext() , somente no next() . O método hasNext() também compara apenas o índice atual com o tamanho da lista. Quando você removeu o penúltimo elemento da lista ( "February" ), isso resultou que a chamada a seguir de hasNext() simplesmente retornou false e terminou a iteração antes que o CME pudesse ser lançado.

No entanto, se você remover qualquer elemento diferente do penúltimo, a exceção teria sido lançada.

Eu acho que a explicação correta é este extrato dos javadocs de ConcurrentModificationExcetion:

Observe que o comportamento fail-fast não pode ser garantido, pois, em geral, é impossível fazer quaisquer garantias concretas na presença de modificação simultânea não sincronizada. Operações fail-fast lançam ConcurrentModificationException com base no melhor esforço. Portanto, seria errado escrever um programa que dependesse dessa exceção para sua correção: ConcurrentModificationException deve ser usado apenas para detectar bugs.

Portanto, se o iterador falhar rapidamente, poderá lançar a exceção, mas não há garantia. Tente replace February com January no seu exemplo e a exceção é lançada (pelo menos no meu ambiente)

O iterador verifica se iterou quantas vezes restarem elementos para ver que chegou ao final antes de verificar se há uma modificação concorrente. Isso significa que, se você remover apenas o segundo último elemento, não verá um CME no mesmo iterador.