Question Itération à travers une collection, en évitant ConcurrentModificationException lors de la suppression en boucle


Nous savons tous que vous ne pouvez pas faire ceci:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... cela fonctionne apparemment parfois, mais pas toujours. Voici un code spécifique:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Ceci, bien sûr, entraîne:

Exception in thread "main" java.util.ConcurrentModificationException

... même si plusieurs threads ne le font pas ... Quoi qu'il en soit.

Quelle est la meilleure solution à ce problème? Comment puis-je supprimer un élément de la collection dans une boucle sans lever cette exception?

J'utilise aussi un arbitraire Collection ici, pas nécessairement un ArrayList, donc vous ne pouvez pas compter sur get.


1028
2017-10-21 23:23


origine


Réponses:


Iterator.remove() est sûr, vous pouvez l'utiliser comme ceci:

List<String> list = new ArrayList<>();

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

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 que l'itération est en cours.

La source: docs.oracle> L'interface de collection


Et de même, si vous avez un ListIterator et veux ajouter articles, vous pouvez utiliser ListIterator#add, pour la même raison que vous pouvez utiliser Iterator#remove- C'est conçu pour le permettre.


1466
2017-10-21 23:27



Cela marche:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

J'ai supposé que puisque une boucle foreach est du sucre syntaxique pour l'itération, l'utilisation d'un itérateur n'aiderait pas ... mais ça vous donne ça .remove() fonctionnalité.


320
2017-10-21 23:26



Avec Java 8, vous pouvez utiliser le nouveau removeIf méthode. Appliqué à votre exemple:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

156
2018-05-28 10:11



Comme la question a déjà été résolue, c'est-à-dire que la meilleure méthode consiste à utiliser la méthode remove de l'objet itérateur, j'entrerai dans les détails de l'endroit où l'erreur "java.util.ConcurrentModificationException" Est lancé.

Chaque classe de collection a une classe privée qui implémente l'interface Iterator et fournit des méthodes comme next(), remove() et hasNext().

Le code pour la suite ressemble à ceci ...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Voici la méthode checkForComodification est mis en œuvre comme

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Donc, comme vous pouvez le voir, si vous essayez explicitement de supprimer un élément de la collection. Il en résulte modCount se différencier de expectedModCount, résultant de l'exception ConcurrentModificationException.


38
2018-05-15 19:57



Vous pouvez soit utiliser l'itérateur directement comme vous l'avez mentionné, soit conserver une deuxième collection et ajouter chaque élément que vous voulez supprimer à la nouvelle collection, puis supprimer tout à la fin. Cela vous permet de continuer à utiliser le type de sécurité de la boucle for-each au prix d'une utilisation accrue de la mémoire et du temps CPU (cela ne devrait pas poser de gros problèmes à moins d'avoir de très grosses listes ou un très vieil ordinateur)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

22
2017-10-21 23:32



Dans de tels cas, un truc commun est (était?) De revenir en arrière:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Cela dit, je suis plus qu'heureux que vous ayez de meilleurs moyens dans Java 8, par exemple. removeIf ou filter sur les cours d'eau.


17
2017-08-29 09:56



Même réponse que Claudius avec une boucle for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

14
2017-08-21 12:39



Avec Collections Eclipse (anciennement GS Collections), la méthode removeIf défini sur MutableCollection marchera:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Avec la syntaxe Java 8 Lambda, ceci peut s'écrire comme suit:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

L'appel à Predicates.cast() est nécessaire ici parce qu'un défaut removeIf méthode a été ajoutée sur le java.util.Collection interface en Java 8.

Remarque: Je suis un committer pour Collections Eclipse.


11
2017-12-18 23:08