web-dev-qa-db-fra.com

Obtention d'une exception ConcurrentModificationException lors de la suppression d'un élément de Java.util.List lors de l'itération de liste?

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(String st:li){
        if(st.equalsIgnoreCase("str3"))
            li.remove("str3");
    }
    System.out.println(li);
}

Lorsque je lance ce code, je vais lancer une exception ConcurrentModificationException.

Il semble que lorsque je supprime l'élément spécifié de la liste, celle-ci ignore que sa taille a été modifiée.

Je me demande s'il s'agit d'un problème courant lié aux collections et à la suppression d'éléments.

58
hguser

Je crois que c’est l’objet de la méthode Iterator.remove () , de pouvoir supprimer un élément de la collection tout en effectuant une itération.

Par exemple:

Iterator<String> iter = li.iterator();
while(iter.hasNext()){
    if(iter.next().equalsIgnoreCase("str3"))
        iter.remove();
}
86
Paul Blessing

La manière dont Java 8 la supprime de la liste sans Iterator est la suivante:

li.removeIf(<predicate>)

c'est à dire.

List<String> li = new ArrayList<String>();
// ...
li = li.removeIf(st -> !st.equalsIgnoreCase("str3"));
20
bigdev.de

Notez que cette exception n'indique pas toujours qu'un objet a été modifié simultanément par un autre thread. Si un seul thread émet une séquence d'appels de méthode qui ne respecte pas le contrat d'un objet, celui-ci peut lever cette exception. Par exemple, si un thread modifie directement une collection alors qu'il parcourt la collection avec un itérateur à échec rapide, l'itérateur renvoie cette exception.

Extrait de http://download.Oracle.com/javase/1.4.2/docs/api/Java/util/ConcurrentModificationException.html

18
Serhiy

oui, les gens le rencontrent - le problème est que vous ne pouvez pas modifier la liste en la parcourant. J'ai utilisé 2 alternatives dans le passé: 

  1. Vous pouvez suivre les index des éléments que vous souhaitez supprimer, puis les supprimer après avoir itéré. 
  2. Vous pouvez également copier tous ceux que vous souhaitez conserver dans une nouvelle liste au fur et à mesure de votre itération, puis supprimer l'ancienne liste une fois l'opération terminée.

ces options supposent que vous devez parcourir la liste pour rechercher les éléments à supprimer, ce qui est utile dans les cas où les éléments de la liste sont des objets complexes avec des propriétés que vous pouvez tester. 

Dans votre cas particulier, vous n'avez même pas besoin de répéter, vous pouvez simplement utiliser removeAll. Regardez l'API ici . Il existe également des méthodes astucieuses telles que keepAll qui rejettent tout ce qui n'est pas dans l'argument. Vous pouvez utiliser des méthodes de type remove/rétention lorsque les objets de la liste implémentent des valeurs égales et que le hashcode est correct. Si vous ne pouvez pas compter sur equals/hashcode pour identifier l'égalité entre les instances de votre application, vous devrez effectuer la suppression vous-même ....

6
hvgotcodes

Vous pouvez faire une copie de la liste dont vous voulez supprimer l’élément directement dans chaque boucle. Pour moi, c'est le moyen le plus simple. Quelque chose comme ça:

for (String stringIter : new ArrayList<String>(myList)) {
    myList.remove(itemToRemove);
}

J'espère que cela vous aidera ..

2
stakahop

Je pense qu'il convient de mentionner la version Java 8

@Test
public void testListCur() {
    List<String> li = new ArrayList<String>();
    for (int i = 0; i < 10; i++) {
        li.add("str" + i);
    }

    li = li.stream().filter(st -> !st.equalsIgnoreCase("str3")).collect(Collectors.toList());

    System.out.println(li);
}
2
gdogaru

Essayez ceci (Java 8):

list.removeIf(condition);
2
Taras Melnyk

ArrayList a le champ modCount - nombre de modifications de la collection

Lorsque vous appelez la méthode iterator(), créez un nouvel objet Itr. Il a le champ expectedModCount. Le champ expectedModCount est initialisé avec la valeur modCount. Quand vous invoquez 

li.remove("str3");

modCount incréments. Quand essayez-vous d'accéder à li via l'itérateur Vérifie que expectedModCount == modCount

et si c'est faux jette ConcurrentModificationException

Par conséquent, si vous obtenez un itérateur et une collection après modification, cet itérateur est considéré comme non valide et vous ne pouvez pas l'utiliser.

1
gstackoverflow

J'ai eu ce problème et je pense que le moyen le plus facile est le même avec la deuxième façon donnée par hvgotcodes.

Vous pouvez également copier tous ceux que vous souhaitez conserver dans une nouvelle liste au fur et à mesure de votre itération, puis supprimer l'ancienne liste une fois l'opération terminée.

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }
    List<String> finalLi = new ArrayList<String>();
    for(String st:li){
        if(st.equalsIgnoreCase("str3")){
            // Do nothing
        } else {
            finalLi.add(st);
        }
    }
    System.out.println(finalLi);
}
1
Envil

Je pense que la meilleure réponse est celle de bigdev.de, mais j'aimerais ajouter quelque chose (par exemple, si l'élément est supprimé d'une liste, vous voudrez peut-être vous connecter quelque part ou quelque chose):

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

list.removeIf(a -> {
                boolean condition = a.equalsIgnoreCase("some condition");
                if(condition)
                    logger.info("Item removed from the list: " + a);
                return condition;
  });
0
rpajaziti

J'ai bouclé d'une manière différente ...

public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(int i=0; i<li.size(); i++)
        if(li.get(i).equalsIgnoreCase("str3"))
            li.remove(i--);

    System.out.println(li);
}
0
Mark Meyers