371 votes

Comment éviter "ConcurrentModificationException" en supprimant des éléments de `ArrayList` tout en l'itérant ?

J'essaie de supprimer certains éléments d'un fichier ArrayList en l'itérant comme ceci :

for (String str : myArrayList) {
    if (someCondition) {
        myArrayList.remove(str);
    }
}

Bien sûr, je reçois un ConcurrentModificationException en essayant de supprimer des éléments de la liste en même temps que l'itération. myArrayList . Existe-t-il une solution simple pour résoudre ce problème ?

604voto

arshajii Points 65653

Utilisez un Iterator et appeler remove() :

Iterator<String> iter = myArrayList.iterator();

while (iter.hasNext()) {
    String str = iter.next();

    if (someCondition)
        iter.remove();
}

7 votes

Merci, maintenant tout fonctionne bien :) Je pense que cette réponse est la meilleure, car le code est facilement lisible.

6 votes

@ErnestasGruodis La contrepartie est que iter est maintenant dans la portée pour le reste de la méthode.

4 votes

Et si je voulais supprimer quelque chose d'autre que l'itération actuelle (disons que c'est sur l'index 2, mais que je dois supprimer l'index 7 en même temps). J'obtiens une ConcurrentModificationException dès que j'essaie de passer par .remove(index).

208voto

Kevin DiTraglia Points 9303

Comme alternative aux réponses des autres, j'ai toujours fait quelque chose comme ça :

List<String> toRemove = new ArrayList<String>();
for (String str : myArrayList) {
    if (someCondition) {
        toRemove.add(str);
    }
}
myArrayList.removeAll(toRemove);

Cela vous évitera de devoir traiter directement l'itérateur, mais nécessite une autre liste. J'ai toujours préféré cette méthode pour une raison ou une autre.

25 votes

+1 J'aime cette solution sans itérateur.

4 votes

@KevinDiTraglia Y a-t-il une raison d'utiliser plus de ressources que nécessaire ? Ce n'est pas comme si les itérateurs étaient si difficiles à travailler ou rendaient le code désordonné.

2 votes

@EricStein Je me retrouve généralement dans des situations où je veux aussi ajouter à la liste, et les ressources supplémentaires sont le plus souvent triviales. C'est juste une solution alternative, les deux ont leurs avantages et leurs inconvénients.

96voto

Fedorov Mikhail Points 103

L'utilisateur de Java 8 peut le faire : list.removeIf(...)

    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    list.removeIf(e -> (someCondition));

Elle supprimera les éléments de la liste pour lesquels une certaine condition est satisfaite.

0 votes

Oui, c'est bien mieux si vous pouvez utiliser Java 8.

2 votes

Ce serait bien s'ils avaient aussi ajouté removeWhile

0 votes

@damluar Je ne comprends pas pourquoi la removeWhile , removeIf supprimera tous les éléments correspondant à la condition.

69voto

Eric Stein Points 4324

Vous devez utiliser la méthode remove() de l'itérateur, ce qui signifie qu'il n'y a pas de boucle for améliorée :

for (final Iterator iterator = myArrayList.iterator(); iterator.hasNext(); ) {
    iterator.next();
    if (someCondition) {
        iterator.remove();
    }
}

10 votes

Je pense que cette réponse communique mieux ; l'itérateur est confiné à la boucle for, et les détails de l'itération sont dans l'instruction for. Moins de bruit visuel.

0 votes

Si vous ajoutez des paramètres de type à l'Iterator et que vous affectez l'iterator.next() à une variable pour pouvoir en faire quelque chose dans le if, c'est la meilleure solution.

0 votes

Pourquoi déclarez-vous l'itérateur comme final ?

41voto

Dima Naychuk Points 1

Non, non, NON !

Dans les tâches à triplet unique, il n'est pas nécessaire d'utiliser l'Iterator, ni même CopyOnWriteArrayList (en raison des pertes de performances).

La solution est beaucoup plus simple : essayer d'utiliser la boucle for canonique au lieu de la boucle for-each .

Selon les détenteurs des droits d'auteur de Java (il y a quelques années Sun, maintenant Oracle) guide pour chaque boucle Il utilise un itérateur pour parcourir la collection et le cache simplement pour améliorer l'apparence du code. Mais, malheureusement, comme nous pouvons le voir, cela a produit plus de problèmes que de bénéfices, sinon ce sujet n'aurait pas été abordé.

Par exemple, ce code entraînera une exception java.util.ConcurrentModificationException lors de la prochaine itération sur une ArrayList modifiée :

        // process collection
        for (SomeClass currElement: testList) {

            SomeClass founDuplicate = findDuplicates(currElement);
            if (founDuplicate != null) {
                uniqueTestList.add(founDuplicate);
                testList.remove(testList.indexOf(currElement));
            }
        }

Mais le code suivant fonctionne très bien :

    // process collection
    for (int i = 0; i < testList.size(); i++) {
        SomeClass currElement = testList.get(i);

        SomeClass founDuplicate = findDuplicates(currElement);
        if (founDuplicate != null) {
            uniqueTestList.add(founDuplicate);
            testList.remove(testList.indexOf(currElement));
            i--; //to avoid skipping of shifted element
        }
    }

Essayez donc d'utiliser l'approche d'indexation pour itérer sur des collections et évitez la boucle for-each, car elles ne sont pas équivalentes ! La boucle for-each utilise certains itérateurs internes, qui vérifient les modifications de la collection et lèvent l'exception ConcurrentModificationException. Pour le confirmer, regardez de plus près la trace de pile imprimée lors de l'utilisation du premier exemple que j'ai posté :

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at TestFail.main(TestFail.java:43)

Pour le multithreading, utilisez les approches multitâches correspondantes (comme le mot-clé synchronisé).

19 votes

Il est intéressant de noter qu'étant donné la façon dont une LinkedList fonctionne en interne, un Iterator est loin plus efficace que les suivantes get(i) appels avec un i .

0 votes

Excellent commentaire et détails, merci

1 votes

D'accord avec Angad. Souvent, on n'a accès qu'au type générique List, et l'implémentation qui a été utilisée est alors inconnue. Si l'implémentation utilisée est LinkedList, l'utilisation d'une boucle for de style C pour parcourir la liste en récupérant chaque élément sera d'une complexité O(n2).

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