web-dev-qa-db-fra.com

ConcurrentModificationException lors de l'ajout à l'intérieur d'une boucle foreach dans ArrayList

J'essaie d'utiliser la boucle foreach avec l'arraylist, mais quand je l'utilise, cela me donne une erreur, mais quand j'utilise la boucle for normal, cela fonctionne parfaitement, quel pourrait être le problème?

Le code est ici:

for (Pair p2 : R) {
    if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), p2.getSecondElm()));
    else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p2.getFirstElm(), p.getSecondElm()));

    // else
    // There are no transitive pairs in R.
}

c'est la boucle qui ne fonctionne pas, et voici celle qui fonctionne:

for (int i = 0; i < R.size(); i++) {
    if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), R.get(i).getSecondElm()));
    else if ((R.get(i).getSecondElm() == p.getFirstElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (R.get(i).getFirstElm(), p.getSecondElm()));
    //else
    //  There are no transitive pairs in R.
}

l'erreur que j'obtiens lorsque j'utilise la boucle foreach est:

Exception in thread "main" Java.util.ConcurrentModificationException
    at Java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at Java.util.AbstractList$Itr.next(Unknown Source)  
    at set.problem.fourth.PoSet.makeTransitive(PoSet.Java:145)  
    at set.problem.fourth.PoSet.addToR(PoSet.Java:87)
    at set.problem.fourth.PoSetDriver.typicalTesting(PoSetDriver.Java:35)
    at set.problem.fourth.PoSetDriver.main(PoSetDriver.Java:13)
22
hakuna matata

Les classes de la collection Java sont résistantes aux défaillances, ce qui signifie que si la collection est modifiée pendant qu'un thread le traverse à l'aide de l'itérateur, le iterator.next() lance un ConcurrentModificationException.

Cette situation peut se produire dans le cas d'un environnement multithread ou à thread unique. - www.javacodegeeks.com

Vous ne pouvez pas modifier un List dans une boucle for/each, Qui est du sucre syntaxique autour du Iterator comme détail d'implémentation. Vous ne pouvez appeler en toute sécurité .remove() qu'en utilisant directement le Iterator.

Notez que Iterator.remove est le seul moyen sûr de modifier une collection pendant l'itération; le comportement n'est pas spécifié si la collection sous-jacente est modifiée d'une autre manière pendant l'itération. - Tutoriel sur les collections Java

L'appel de .add() dans la boucle for/each Modifie le contenu, et le Iterator utilisé en arrière-plan le voit et lève cette exception.

Une préoccupation plus subtile est que la deuxième façon que vous listez, le .size() augmente à chaque fois que vous .add() donc vous finirez par traiter toutes les choses que vous .add(), cela pourrait éventuellement provoquer une boucle sans fin en fonction de ce que les données d'entrée sont. Je ne sais pas si c'est ce que vous désirez.

Solution

Je créerais un autre ArrayList et .add() pour toutes les nouvelles choses, puis après la boucle, utiliser .addAll() sur le ArrayList d'origine pour combiner les deux listes ensemble. Cela rendra les choses explicites dans ce que vous essayez de faire, c'est-à-dire à moins que votre intention ne traite toutes les choses nouvellement ajoutées au fur et à mesure que vous les ajoutez.

Solution 2014:

Utilisez toujours les classes de collections Immutable et créez de nouvelles classes de collection Immutable au lieu d'essayer d'en modifier une seule partagée. C'est essentiellement ce que dit ma réponse de 2012, mais je voulais le rendre plus explicite.

La goyave le supporte très bien, utilisez ImmutableList.copyOf() pour faire circuler les données.

Utilisez Iterables.filter() pour filtrer les éléments dans un nouveau ImmutableList, sans état mutable partagé, ce qui signifie aucun problème de concurrence!

36
user177800

Sous le capot, la boucle for-each dans Java utilise un Iterator pour parcourir la collection (voir ceci article pour une explication détaillée.) Et l'itérateur lancera un ConcurrentModificationException si vous modifiez la collection tout en itérant dessus, voyez ceci post .

3
Óscar López

Le problème est que vous faites le R.add () dans la première ligne de la boucle.

Dans la première situation, vous avez un itérateur ouvert à l'arrayliste. Lorsque vous effectuez un ajout, puis essayez à nouveau d'itérer, l'itérateur remarque que la structure des données a changé sous vous.

Dans le cas du look for, vous obtenez simplement un nouvel élément à chaque fois et n'avez pas le problème de modification simultanée, bien que la taille change à mesure que vous ajoutez d'autres éléments.

Pour résoudre le problème, vous souhaitez probablement ajouter à un emplacement temporaire et l'ajouter après la boucle ou faire une copie des données initiales et ajouter à l'original.

1
Paul Rubel