web-dev-qa-db-fra.com

Comment éviter "ConcurrentModificationException" lors de la suppression d'éléments de `ArrayList` lors de son itération?

J'essaie de supprimer certains éléments d'un ArrayList en le répétant comme suit:

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

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

320
Ernestas Gruodis

Utilisez un Iterator et appelez remove() :

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

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

    if (someCondition)
        iter.remove();
}
531
arshajii

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 d'avoir à traiter directement avec l'itérateur, mais nécessite une autre liste. J'ai toujours préféré cette route pour une raison quelconque.

170
Kevin DiTraglia

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

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

Cela supprimera des éléments de la liste pour lesquels certaines conditions sont satisfaites

84
Mikhail Boyarsky

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

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

Non non Non!

Dans les tâches simples traitées, vous n'avez pas besoin d'utiliser Iterator, par ailleurs, CopyOnWriteArrayList (en raison de l'impact négatif sur les performances).

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

Selon Java détenteurs des droits d'auteur (il y a quelques années, Sun, maintenant Oracle) pour chaque guide de boucle , il utilise un itérateur pour parcourir la collection et le cache pour améliorer l'apparence du code. Malheureusement, comme nous pouvons le constater, cela a engendré plus de problèmes que de profits, sinon ce sujet ne se poserait pas.

Par exemple, ce code mènera à une exception Java.util.ConcurrentModificationException lors de la prochaine itération sur la liste de tableaux 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 parcourir les collections et évitez les boucles pour chaque boucle, car elles ne sont pas équivalentes! For-each boucle utilise des itérateurs internes, qui vérifient la modification de la collection et lèvent une exception ConcurrentModificationException. Pour confirmer cela, examinez de plus près le tracé de la pile imprimé lorsque vous utilisez le 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 (telles que le mot clé synchronisé).

32
Dima Naychuk

Tandis que les autres solutions suggérées fonctionnent, si vous voulez vraiment que la solution soit sécurisée pour le thread, vous devez remplacer ArrayList par CopyOnWriteArrayList

    //List<String> s = new ArrayList<>(); //Will throw exception
    List<String> s = new CopyOnWriteArrayList<>();
    s.add("B");
    Iterator<String> it = s.iterator();
    s.add("A");

    //Below removes only "B" from List
    while (it.hasNext()) {
        s.remove(it.next());
    }
    System.out.println(s);
8
Prashant Bhate

Une autre méthode consiste à convertir votre List en array, à les itérer et à les supprimer directement de la List en fonction de votre logique.

List<String> myList = new ArrayList<String>(); // You can use either list or set

myList.add("abc");
myList.add("abcd");
myList.add("abcde");
myList.add("abcdef");
myList.add("abcdefg");

Object[] obj = myList.toArray();

for(Object o:obj)  {
    if(condition)
        myList.remove(o.toString());
}
7
CarlJohn

Si vous souhaitez modifier votre liste pendant le parcours, vous devez utiliser le paramètre Iterator. Et vous pouvez ensuite utiliser iterator.remove() pour supprimer les éléments pendant le parcours.

6
Juned Ahsan
List myArrayList  = Collections.synchronizedList(new ArrayList());

//add your elements  
 myArrayList.add();
 myArrayList.add();
 myArrayList.add();

synchronized(myArrayList) {
    Iterator i = myArrayList.iterator(); 
     while (i.hasNext()){
         Object  object = i.next();
     }
 }
6
Prabhakaran

Vous pouvez utiliser la fonction iterator remove () pour supprimer l'objet de l'objet de collection sous-jacent. Mais dans ce cas, vous pouvez supprimer le même objet et aucun autre objet de la liste.

de ici

1
gilo