web-dev-qa-db-fra.com

Comment la méthode de suppression d'itérateur supprime réellement un objet

Nous savons tous que la façon la plus sûre et probablement la seule de supprimer un objet d'une collection tout en l'itérant est de récupérer d'abord le Iterator, d'effectuer une boucle et de le supprimer si nécessaire;

Iterator iter=Collection.iterator();
while(iter.hasNext()){
    Object o=iter.next()
    if(o.equals(what i'm looking for)){
        iter.remove();
    }
}

Ce que je voudrais comprendre, et malheureusement je n'ai pas trouvé d'explication technique approfondie, c'est comment cette suppression est effectuée,
Si:

for(Object o:myCollection().getObjects()){
    if(o.equals(what i'm looking for)){
        myCollection.remove(o);
    }
}

Va lancer un ConcurrentModificationException, que fait "en termes techniques" Iterator.remove()? Est-ce qu'il supprime l'objet, rompt la boucle et redémarre la boucle?

Je vois dans la documentation officielle:

"Supprime l'élément en cours. Lance IllegalStateException si une tentative est faite d'appeler remove() qui n'est pas précédée d'un appel à next ()."

La partie "supprime l'élément courant", me fait penser exactement à la même situation se produisant dans une boucle "régulière" => (effectuer un test d'égalité et supprimer si nécessaire), mais pourquoi la boucle Iterator ConcurrentModification-safe?

26
JBoy

La manière exacte dont Iterator supprime les éléments dépend de son implémentation, qui peut être différente pour différentes collections. Certainement, cela ne rompt pas la boucle dans laquelle vous êtes. Je viens de voir comment l'itérateur ArrayList est implémenté et voici le code:

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

Il vérifie donc les modifications simultanées, supprime l'élément à l'aide de la méthode publique ArrayList remove et incrémente le compteur des modifications de liste afin que ConcurrentModificationException ne soit pas levé à la prochaine itération.

18
Grzegorz Olszewski

La raison pour laquelle vous ne pouvez pas modifier une liste pendant son itération est que l'itérateur doit savoir quoi retourner pour hasNext () et next ().

La façon dont cela est fait est spécifique à l'implémentation, mais vous pouvez consulter le code source de ArrayList/AbstractList/LinkedList etc.

Notez également que dans certaines situations, vous pouvez utiliser du code comme celui-ci comme alternative:

List<Foo> copyList = new ArrayList<>(origList);
for (Foo foo : copyList){
  if (condition){
    origList.remove(foo);
  }
}

Mais ce code s'exécutera probablement un peu plus lentement car la collection doit être copiée (copie superficielle uniquement) et l'élément à supprimer doit être recherché.

Notez également que si vous utilisez directement l'itérateur, il est recommandé d'utiliser une boucle for au lieu d'une boucle while car cela limite la portée de la variable:

for (Iterator<Foo> iterator = myCollection.iterator(); iterator.hasNext();){
...
}
20
Puce