Comment éviter java.util.ConcurrentModificationException lors de l'itération et de la suppression d'éléments d'une ArrayList

203

J'ai une ArrayList sur laquelle je veux répéter. Tout en itérant dessus, je dois supprimer des éléments en même temps. De toute évidence, cela jette un java.util.ConcurrentModificationException.

Quelle est la meilleure pratique pour gérer ce problème? Dois-je d'abord cloner la liste?

Je supprime les éléments non pas dans la boucle elle-même mais dans une autre partie du code.

Mon code ressemble à ceci:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomethingpourrait appeler Test.removeA();

Belphegor
la source

Réponses:

325

Deux options:

  • Créez une liste de valeurs que vous souhaitez supprimer, en ajoutant à cette liste dans la boucle, puis appelez originalList.removeAll(valuesToRemove)à la fin
  • Utilisez la remove()méthode sur l'itérateur lui-même. Notez que cela signifie que vous ne pouvez pas utiliser la boucle for améliorée.

Comme exemple de la deuxième option, supprimer toutes les chaînes d'une longueur supérieure à 5 d'une liste:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}
Jon Skeet
la source
2
J'aurais dû mentionner que je supprime les éléments dans une autre partie du code et non la boucle elle-même.
RoflcoptrException
@Roflcoptr: Eh bien, il est difficile de répondre sans voir comment les deux bits de code interagissent. En gros, vous ne pouvez pas faire ça. Il n'est pas évident que le clonage de la liste en premier serait utile, sans voir comment tout se tient. Pouvez-vous donner plus de détails dans votre question?
Jon Skeet du
Je sais que le clonage de la liste aiderait, mais je ne sais pas si c'est une bonne approche. Mais je vais ajouter un peu plus de code.
RoflcoptrException
2
Cette solution conduit également à java.util.ConcurrentModificationException, voir stackoverflow.com/a/18448699/2914140 .
CoolMind
1
@CoolMind: Sans plusieurs threads, ce code devrait convenir.
Jon Skeet
17

À partir des JavaDocs de la ArrayList

Les itérateurs renvoyés par les méthodes iterator et listIterator de cette classe sont résistants aux défaillances: si la liste est structurellement modifiée à tout moment après la création de l'itérateur, de quelque manière que ce soit, sauf par le biais des méthodes remove ou add de l'itérateur, l'itérateur lèvera une exception ConcurrentModificationException.

Varun Achar
la source
6
et où est la réponse à la question?
Adelin
Comme il est dit, sauf par les propres méthodes de suppression ou d'ajout de l'itérateur
Varun Achar
14

Vous essayez de supprimer la valeur de la liste dans la boucle avancée "for", ce qui n'est pas possible, même si vous appliquez une astuce (ce que vous avez fait dans votre code). Le meilleur moyen est de coder le niveau de l'itérateur comme indiqué ici.

Je me demande comment les gens n'ont pas suggéré l'approche traditionnelle de la boucle.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

Cela fonctionne aussi.

suhas0sn07
la source
2
Ce n'est pas correct !!! lorsque vous supprimez un élément, le suivant prend sa position et pendant que i augmente, l'élément suivant n'est pas vérifié dans l'itération suivante. Dans ce cas, vous devriez opter pour (int i = lStringList.size (); i> -1; i--)
Johntor
1
Se mettre d'accord! L'alternative consiste à effectuer i--; dans la condition if dans la boucle.
suhas0sn07
Je pense que cette réponse a été modifiée pour répondre aux problèmes dans les commentaires ci-dessus, donc comme elle fonctionne maintenant, du moins pour moi.
Kira Resari
11

Vous devez vraiment simplement réitérer le tableau de la manière traditionnelle

Chaque fois que vous supprimez un élément de la liste, les éléments suivants seront repoussés. Tant que vous ne modifiez pas d'autres éléments que l'itération, le code suivant devrait fonctionner.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}
Marcus
la source
10

Dans Java 8, vous pouvez utiliser l'interface de collecte et le faire en appelant la méthode removeIf:

yourList.removeIf((A a) -> a.value == 2);

Plus d'informations peuvent être trouvées ici

ggeo
la source
6

Faites la boucle normalement, java.util.ConcurrentModificationExceptionc'est une erreur liée aux éléments auxquels vous accédez.

Alors essayez:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}
Tacila
la source
Vous avez évité le java.util.ConcurrentModificationExceptionen ne supprimant rien de la liste. Rusé. :) Vous ne pouvez pas vraiment appeler cela "la voie normale" pour parcourir une liste.
Zsolt Sky
6

Lors de l'itération de la liste, si vous souhaitez supprimer l'élément est possible. Voyons ci-dessous mes exemples,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

J'ai les noms ci-dessus de la liste des tableaux. Et je veux supprimer le nom "def" de la liste ci-dessus,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

Le code ci-dessus lève l' exception ConcurrentModificationException car vous modifiez la liste lors de l'itération.

Donc, pour supprimer le nom "def" d'Arraylist en procédant de cette façon,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

Le code ci-dessus, via l'itérateur, nous pouvons supprimer le nom "def" de la liste Arraylist et essayer d'imprimer le tableau, vous verrez la sortie ci-dessous.

Sortie: [abc, ghi, xyz]

Indra K
la source
Sinon, nous pouvons utiliser une liste simultanée qui est disponible dans un package simultané, afin que vous puissiez effectuer des opérations de suppression et d'ajout pendant l'itération. Par exemple, consultez l'extrait de code ci-dessous. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (noms); for (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K
CopyOnWriteArrayList va être les opérations les plus coûteuses.
Indra K
5

Une option consiste à modifier la removeAméthode en ceci -

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

Mais cela signifierait que vous doSomething()devriez pouvoir passer iteratorà la removeméthode. Ce n'est pas une bonne idée.

Pouvez-vous le faire en deux étapes: dans la première boucle lorsque vous parcourez la liste, au lieu de supprimer les éléments sélectionnés, marquez- les comme devant être supprimés . Pour cela, vous pouvez simplement copier ces éléments (copie superficielle) dans un autre List.

Ensuite, une fois votre itération terminée, faites simplement a removeAllpartir de la première liste tous les éléments de la seconde liste.

Bhaskar
la source
Excellent, j'ai utilisé la même approche, même si je boucle deux fois. cela rend les choses simples et sans problèmes simultanés avec lui :)
Pankaj Nimgade
1
Je ne vois pas qu'Iterator a une méthode remove (a). La suppression () ne prend aucun argument docs.oracle.com/javase/8/docs/api/java/util/Iterator.html que me manque-t-il?
c0der
5

Voici un exemple où j'utilise une liste différente pour ajouter les objets à supprimer, puis j'utilise stream.foreach pour supprimer des éléments de la liste d'origine:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}
serup
la source
Je pense que vous faites un travail supplémentaire en exécutant deux boucles, dans le pire des cas, les boucles feraient partie de la liste entière. Ce serait plus simple et moins coûteux de le faire en une seule boucle.
Luis Carlos
Je ne pense pas que vous puissiez supprimer un objet de la première boucle, d'où la nécessité d'une boucle de suppression supplémentaire, également la boucle de suppression n'est que des objets à supprimer - peut-être pourriez-vous écrire un exemple avec une seule boucle, je voudrais le voir - merci @ LuisCarlos
serup
Comme vous le dites avec ce code, vous ne pouvez supprimer aucun élément à l'intérieur de la boucle for car il provoque l'exception java.util.ConcurrentModificationException. Cependant, vous pouvez utiliser une base pour. Ici, j'écris un exemple en utilisant une partie de votre code.
Luis Carlos
1
for (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - customersTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} Est important i-- parce que vous ne voulez ignorer aucun élément. Vous pouvez également utiliser la méthode removeIf (filtre Predicate <? Super E>) fournie par la classe ArrayList. J'espère que cette aide
Luis Carlos
1
L'exception se produit car dans for-loop, il est une référence active à l'itérateur de la liste. Normalement, il n'y a pas de référence et vous avez plus de flexibilité pour modifier les données. J'espère que cette aide
Luis Carlos
3

Au lieu d'utiliser Pour chaque boucle, utilisez la boucle normale. par exemple, le code ci-dessous supprime tous les éléments de la liste de tableaux sans donner java.util.ConcurrentModificationException. Vous pouvez modifier la condition dans la boucle en fonction de votre cas d'utilisation.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }
Shubham Chopra
la source
2

Faites quelque chose de simple comme ceci:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}
Xlsx
la source
2

Une solution alternative Java 8 utilisant stream:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

Dans Java 7, vous pouvez utiliser Guava à la place:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

Notez que l'exemple de Guava se traduit par une liste immuable qui peut ou non être ce que vous voulez.

Zsolt Sky
la source
1

Vous pouvez également utiliser CopyOnWriteArrayList au lieu d'une ArrayList. Il s'agit de la dernière approche recommandée à partir de JDK 1.5.

Pathikreet
la source
1

Dans mon cas, la réponse acceptée ne fonctionne pas, elle arrête l'exception mais elle provoque une incohérence dans ma liste. La solution suivante fonctionne parfaitement pour moi.

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

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

Dans ce code, j'ai ajouté les éléments à supprimer, dans une autre liste, puis utilisé la list.removeAllméthode pour supprimer tous les éléments requis.

Asad Ali Choudhry
la source
0

"Dois-je d'abord cloner la liste?"

Ce sera la solution la plus simple, supprimez du clone et recopiez le clone après la suppression.

Un exemple de mon jeu de rummikub:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}
Arjen Rodenhuis
la source
2
Les downvoters devraient au moins laisser un commentaire avant de downvoter.
OneWorld
2
Il n'y a rien de fondamentalement mauvais avec cette réponse, attendez-vous à ce que cela stones = (...) clone.clone();soit superflu. Ne ferait pas de stones = clone;même?
vikingsteve
Je suis d'accord, le deuxième clonage n'est pas nécessaire. Vous pouvez encore simplifier cela en itérant sur le clone et en supprimant directement les éléments stones. De cette façon, vous n'avez même pas besoin de la clonevariable: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky
0

Si votre objectif est de supprimer tous les éléments de la liste, vous pouvez parcourir chaque élément, puis appeler:

list.clear()
Gibolt
la source
0

J'arrive en retard je sais mais je réponds à cela car je pense que cette solution est simple et élégante:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

Tout cela est pour la mise à jour d'une liste à l'autre et vous pouvez tout faire à partir d'une seule liste et dans la mise à jour de la méthode, vous vérifiez les deux listes et pouvez effacer ou ajouter des éléments entre la liste. Cela signifie que les deux affichent toujours la même taille

Devenez Arguello Flores
la source
0

Utiliser Iterator au lieu de Array List

Faire convertir un ensemble en itérateur avec correspondance de type

Et passer à l'élément suivant et supprimer

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

Le passage au suivant est important ici car il devrait prendre l'index pour supprimer l'élément.

user8009263
la source
0

Qu'en est-il de

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());
joseluisbz
la source
-3

Ajoutez simplement une pause après votre instruction ArrayList.remove (A)

Sebastian Altamirano
la source
Pourriez-vous ajouter quelques explications?
xskxzr