174 votes

Pourquoi n'ai-je pas de java.util.ConcurrentModificationException dans cet exemple ?

Note : Je suis conscient de la Iterator#remove() méthode.

Dans l'exemple de code suivant, je ne comprends pas pourquoi l'option List.remove en main la méthode jette ConcurrentModificationException mais no en el remove méthode.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

259voto

pushy Points 4646

Voici pourquoi : Comme il est dit dans la Javadoc :

Les itérateurs renvoyés par les itérateurs et listIterator de cette classe. sont infaillibles : si la liste est structurellement modifiée à n'importe quel moment après la création de l'itérateur, d'une manière autre que par l'intermédiaire de l'itérateur. structurellement à n'importe quel moment après la création de l'itérateur, sauf par méthodes remove ou add de l'itérateur lui-même, l'itérateur déclenchera une ConcurrentModificationException.

Cette vérification est effectuée dans le next() de l'itérateur (comme vous pouvez le voir dans la trace de la pile). Mais nous atteindrons la méthode next() uniquement si hasNext() a livré true, ce qui est ce qui est appelé par le for each pour vérifier si la limite est respectée. Dans votre méthode remove, lorsque hasNext() vérifie s'il a besoin de renvoyer un autre élément, il verra qu'il a renvoyé deux éléments, et maintenant après qu'un élément ait été retiré, la liste ne contient plus que deux éléments. Tout va donc pour le mieux et nous avons terminé l'itération. La vérification des modifications simultanées n'a pas lieu, car elle est effectuée dans la fonction next() qui n'est jamais appelée.

Ensuite, nous arrivons à la deuxième boucle. Après avoir supprimé le deuxième numéro, la méthode hasNext vérifie à nouveau si elle peut renvoyer d'autres valeurs. Elle a déjà renvoyé deux valeurs, mais la liste ne contient plus qu'une seule valeur. Mais le code est ici :

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

1 != 2, donc on continue jusqu'au next() qui se rend maintenant compte que quelqu'un a manipulé la liste et déclenche l'exception.

J'espère que cela répond à votre question.

Résumé

List.remove() ne jettera pas ConcurrentModificationException lorsqu'il supprime l'avant-dernier élément de la liste.

42voto

Jam Points 13872

Une façon de gérer cela est de retirer quelque chose d'une copie d'une Collection (pas la collection elle-même), le cas échéant. Clone la collection d'origine pour en faire une copie via une Constructor .

Cette exception peut être levée par des méthodes qui ont détecté la modification simultanée d'un objet alors qu'une telle modification n'est pas autorisée.

Pour votre cas spécifique, tout d'abord, je ne pense pas que final est une bonne solution si vous avez l'intention de modifier la liste après la déclaration.

private static final List<Integer> integerList;

Envisagez également de modifier une copie au lieu de la liste originale.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

13voto

RightHandedMonkey Points 614

La méthode forward/iterator ne fonctionne pas lors de la suppression d'éléments. Vous pouvez supprimer l'élément sans erreur, mais vous obtiendrez une erreur d'exécution lorsque vous tenterez d'accéder aux éléments supprimés. Vous ne pouvez pas utiliser l'itérateur car, comme le montre pushy, cela provoquera une ConcurrentModificationException. Utilisez donc une boucle for normale à la place, mais reculez dans la boucle.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Une solution :

Parcourez le tableau dans l'ordre inverse si vous voulez supprimer un élément de la liste. En parcourant simplement la liste à l'envers, vous évitez de visiter un élément qui a été supprimé, ce qui supprime l'exception.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

7voto

Bhushan Points 3461

Cet extrait va toujours lance une ConcurrentModificationException.

La règle est la suivante : "Vous ne pouvez pas modifier (ajouter ou supprimer des éléments de la liste) tout en itérant sur celle-ci à l'aide d'un Iterator (ce qui se produit lorsque vous utilisez une boucle for-each)".

JavaDocs :

Les itérateurs renvoyés par les méthodes iterator et listIterator de cette classe sont infaillibles : si la structure de la liste est modifiée à un moment quelconque après la création de l'itérateur, de quelque manière que ce soit, sauf par les méthodes remove ou add de l'itérateur lui-même, l'itérateur lèvera une ConcurrentModificationException.

Par conséquent, si vous voulez modifier la liste (ou toute collection en général), utilisez l'itérateur, car il est alors conscient des modifications et celles-ci seront traitées correctement.

J'espère que cela vous aidera.

5voto

Gondil Points 23

J'ai eu le même problème mais dans le cas où j'ajoutais un élément dans une liste itérée. Je l'ai fait de cette façon

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Maintenant tout va bien parce que vous ne créez pas d'itérateur sur votre liste, vous l'itérez "manuellement". Et la condition i < integerList.size() ne vous trompera jamais car lorsque vous supprimez/ajoutez quelque chose dans la liste, la taille de la liste diminue/augmente

J'espère que cela vous aidera, pour moi c'était la solution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X