Pourquoi est-ce que je n'obtiens pas d'exception java.util.ConcurrentModificationException dans cet exemple?

176

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

Dans l'exemple de code suivant, je ne comprends pas pourquoi la méthode List.removein mainlève ConcurrentModificationException, mais pas dans la removemé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);
            }
        }
    }
}
Bhesh Gurung
la source
3
Le seul moyen sûr de supprimer un élément d'une liste lors d'une itération sur cette liste est d'utiliser Iterator#remove(). Pourquoi le faites-vous de cette façon?
Matt Ball
@MattBall: J'essayais juste de voir quelle pourrait être la raison ici. Parce que c'est la même "boucle for améliorée" dans les deux méthodes, mais l'une lance ConcurrentModificationExceptionet l'autre non.
Bhesh Gurung
Il y a une différence dans l'élément que vous supprimez. Dans la méthode, vous supprimez «l'élément du milieu». Dans l'ensemble, vous supprimez le dernier. Si vous échangez les numéros, vous obtenez l'exception dans votre méthode. Je ne sais toujours pas pourquoi.
Ben van Gompel
J'ai eu un problème similaire, lorsque ma boucle a également itéré une position qui n'existait pas après avoir supprimé un élément de la boucle. J'ai simplement corrigé cela en ajoutant un return;dans la boucle.
frank17
sur java8 Android, la suppression d'un élément autre que le dernier invoquerait l'exception ConcurrentModificationException. donc pour votre cas, la fonction de suppression obtiendrait une exception opposée à celle que vous avez observée auparavant.
gonglong

Réponses:

262

Voici pourquoi: Comme il est dit dans le Javadoc:

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

Cette vérification se fait dans la next()méthode de l'itérateur (comme vous pouvez le voir par le stacktrace). Mais nous n'atteindrons la next()méthode que si elle est hasNext()livrée true, ce qui est appelé par le for each pour vérifier si la limite est respectée. Dans votre méthode remove, lorsque vous hasNext()vérifiez s'il a besoin de retourner un autre élément, il verra qu'il a renvoyé deux éléments, et maintenant, après qu'un élément a été supprimé, la liste ne contient que deux éléments. Donc tout est pêche et nous en avons terminé avec les itérations. La vérification des modifications simultanées n'a pas lieu, car elle est effectuée dans la next()méthode qui n'est jamais appelée.

Ensuite, nous arrivons à la deuxième boucle. Après avoir supprimé le deuxième nombre, la méthode hasNext vérifiera à nouveau si elle peut renvoyer plus 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 à la next()méthode, qui se rend compte maintenant que quelqu'un a joué avec la liste et déclenche l'exception.

J'espère que cela clarifie votre question.

Résumé

List.remove()ne lancera pas ConcurrentModificationExceptionlorsqu'il supprime l'avant-dernier élément de la liste.

arrogant
la source
5
@pushy: Seules les réponses semblent répondre à ce que la question pose réellement, et l'explication est bonne. J'accepte cette réponse ainsi que +1. Merci.
Bhesh Gurung
42

Une façon de le gérer consiste à supprimer quelque chose d'une copie d'une Collection(et non de la collection elle-même), le cas échéant. Clonela collection originale pour en faire une copie via un fichier Constructor.

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

Pour votre cas spécifique, tout d'abord, je ne pense pas que ce finalsoit une façon de procéder étant donné que vous avez l'intention de modifier la liste après la déclaration

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);
    }
}
James Raitsev
la source
14

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 insistant, cela provoquera une ConcurrentModificationException, utilisez donc une boucle for régulière à la place, mais revenez 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);

Une solution:

Parcourez le tableau dans l'ordre inverse si vous souhaitez supprimer un élément de liste. En revenant simplement dans la liste, 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);
    }
}
RightHandedMonkey
la source
idée brillante !
dobrivoje
7

Cet extrait de code lèvera toujours une ConcurrentModificationException.

La règle est "Vous ne pouvez pas modifier (ajouter ou supprimer des éléments de la liste) tout en l'itérant à 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 sont très rapides: si la liste est modifiée structurellement à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par les propres méthodes remove ou add de l'itérateur, l'itérateur lancera une ConcurrentModificationException.

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

J'espère que cela t'aides.

Bhushan
la source
3
L'OP indique clairement que l'une des boucles ne lève PAS d'exception et l'interrogateur explique pourquoi cela s'est produit.
madth3
qu'entendez-vous par «interrogateur»?
Bhushan
4

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 se passe bien car 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 taille de la liste, décrémentez / incrémentez.

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

Gondil
la source
Ce n'est pas vrai ! Preuve: exécutez cet extrait pour voir le résultat: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Code Complet"); listOfBooks.add ("Code 22"); listOfBooks.add ("22 Effectif"); listOfBooks.add ("Netbeans 33"); System.err.println ("Avant de supprimer:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Après suppression:" + listOfBooks); }
dobrivoje
1

Si vous utilisez des collections de copie sur écriture, cela fonctionnera; cependant, lorsque vous utilisez list.iterator (), l'itérateur retourné référencera toujours la collection d'éléments telle qu'elle était lorsque (comme ci-dessous) list.iterator () a été appelée, même si un autre thread modifie la collection. Toutes les méthodes de mutation appelées sur un Iterator ou ListIterator basé sur la copie sur écriture (comme ajouter, définir ou supprimer) lèveront 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);
            }
        }
    }
}
JohnnyO
la source
0

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

~%

battosai
la source
Désolé pour la faute de frappe Cela 'fonctionne' bien sur Java 1.6
battosai
Hmm ... Peut-être que vous avez une implémentation différente. Mais selon les spécifications, il est censé le faire, l'OMI. Regardez la réponse de @ Pushy.
Bhesh Gurung du
malheureusement, id ne fonctionne pas sur java 1.8
dobrivoje
0

Dans mon cas, je l'ai fait comme ceci:

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

Remplacez Iterator for eachpar for looppour résoudre.

Et la raison est:

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

- Documents Java référencés.

Stephen
la source
-1

Vérifiez votre code man ...

Dans la méthode principale, vous essayez de supprimer le 4ème élément qui n'est pas là et donc l'erreur. Dans la méthode remove (), vous essayez de supprimer le 3ème élément qui est là et donc aucune erreur.

Abhishek
la source
Vous vous trompez: les nombres 2et 3ne sont pas des indices pour la liste, mais des éléments. Les deux logiques de suppression vérifient equalsles éléments de la liste, et non l'index des éléments. De plus, s'il était lié à un index, ce ne le serait IndexOutOfBoundsExceptionpas ConcurrentModificationException.
Malte Hartwig