web-dev-qa-db-fra.com

Pourquoi ne reçois-je pas une exception Java.util.ConcurrentModificationException dans cet exemple?

Remarque: je connais la méthode Iterator#remove().

Dans l'exemple de code suivant, je ne comprends pas pourquoi la méthode List.remove dans main lève ConcurrentModificationException, mais pas dans la méthode remove.

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);
            }
        }
    }
}
173
Bhesh Gurung

Voici pourquoi: Comme il est dit dans la Javadoc:

Les itérateurs retournés par les méthodes iterator et listIterator de cette classe sont ultra-rapides: si la liste est structurellement modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par le biais de ses propres méthodes remove et add, l'itérateur lève une exception ConcurrentModificationException.

Cette vérification est effectuée dans la méthode next() de l'itérateur (comme vous pouvez le voir avec le stacktrace). Mais nous n'atteindrons la méthode next() que si hasNext() est vrai, ce qui est appelé par le pour chacun pour vérifier si la limite est respectée. Dans votre méthode remove, lorsque hasNext() vérifie s'il doit renvoyer un autre élément, il verra qu'il a renvoyé deux éléments. Désormais, après qu'un élément ait été supprimé, la liste ne contient que deux éléments. Donc tout est pêche et nous en avons fini avec les itérations. La vérification des modifications simultanées ne se produit pas, comme cela est fait dans la méthode next() qui n'est jamais appelée.

Nous arrivons ensuite à la deuxième boucle. Une fois le deuxième nombre supprimé, la méthode hasNext vérifie à nouveau si elle peut renvoyer davantage de valeurs. Il a déjà renvoyé deux valeurs, mais la liste n'en contient plus qu'une. Mais le code ici est:

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

1! = 2, nous continuons donc avec la méthode next(), qui réalise maintenant que quelqu'un a manipulé la liste et déclenche l'exception.

J'espère que cela efface votre question.

Sommaire

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

258
pushy

Une façon de le manipuler consiste à supprimer quelque chose d'une copie d'une Collection (pas de Collection elle-même), le cas échéant. Clone la collection d'origine il faut faire une copie via un Constructor.

Cette exception peut être levée par des méthodes ayant détecté une modification simultanée d'un objet lorsque cette modification n'est pas autorisée.

Premièrement, dans votre cas particulier, je ne pense pas que final soit envisageable, compte tenu de votre intention de modifier la liste des déclarations passées.

private static final List<Integer> integerList;

Pensez également à modifier une copie au lieu de la liste d'origine.

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

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

La méthode de transfert/itérateur 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 l'indique l'incident, il provoquera une exception ConcurrentModificationException. Vous devez donc utiliser une boucle for régulière à la place, mais revenir en arrière.

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

ne solution:

Parcourez le tableau dans l'ordre inverse si vous souhaitez supprimer un élément de la liste. En retournant simplement dans la liste, vous évitez de visiter un élément 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);
    }
}
13
RightHandedMonkey

Cet extrait va toujours émettre une exception ConcurrentModificationException.

La règle est la suivante: "Vous ne pouvez pas modifier (ajouter ou supprimer des éléments de la liste) tout en effectuant une itération à l'aide d'un itérateur (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 ont la capacité d'échec rapide: si la liste est structurellement modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sans la suppression ou l'ajout de l'itérateur méthodes, l’itérateur lève une exception ConcurrentModificationException.

Par conséquent, si vous souhaitez modifier la liste (ou toute collection en général), utilisez iterator, car il est alors au courant des modifications et celles-ci seront donc traitées correctement.

J'espère que cela t'aides.

6
Bhushan

J'ai eu le même problème, mais au cas où j'ajouterais un élément dans la 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 car vous ne créez aucun itérateur sur votre liste, vous le parcourez "manuellement". Et la condition i < integerList.size() ne vous trompera jamais car lorsque vous supprimez/ajoutez quelque chose dans la taille de la liste, la décrémentation/incrémentation de la liste ..

J'espère que ça aide, pour moi c'était la solution.

5
Gondil

Remplacez Itérateur for each par for loop à résoudre.

et la raison est:

Les itérateurs retournés par les méthodes iterator et listIterator de cette classe sont ultra-rapides: si la liste est structurellement modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par le biais de ses propres méthodes remove et add, l'itérateur lève une exception ConcurrentModificationException.

- Renvoyé Java Docs.

1
Stephen

Cela fonctionne très bien sur Java 1.6.

~% javac RemoveListElementDemo.Java
~% Java RemoveListElementDemo
~% cat RemoveListElementDemo.Java

import Java.util.*;
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 remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

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

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

~%

1
battosai

Si vous utilisez des collections de copie sur écriture, cela fonctionnera; Cependant, lorsque vous utilisez list.iterator (), l'Iterator renvoyé référencera toujours la collection d'éléments telle qu'elle était quand (comme ci-dessous) list.iterator () était appelé, même si un autre thread modifiait la collection. Toute méthode de mutation appelée sur un Iterator ou ListIterator basé sur la copie sur écriture (telle que ajouter, définir ou supprimer) lève une exception UnsupportedOperationException.

import Java.util.List;
import Java.util.concurrent.CopyOnWriteArrayList;

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

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

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

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

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

Dans mon cas je l'ai fait comme ça:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
0
Saif Hamed