Java >> Tutoriel Java >  >> Tag >> while

Comment gérer ConcurrentModificationException en Java ? Méfiez-vous lors de la suppression d'éléments de ArrayList en boucle

L'un des problèmes courants lors de la suppression d'éléments d'une ArrayList en Java est l'exception ConcurrentModificationException. Si vous utilisez une boucle for classique avec l'index ou une boucle for améliorée et essayez de supprimer un élément de la ArrayList en utilisant remove() méthode, vous obtiendrez le ConcurrentModificationException mais si vous utilisez la méthode de suppression d'Iterator ou celle de ListIterator
remove() méthode, vous n'obtiendrez pas cette erreur et ne pourrez pas supprimer l'élément. C'est une règle non écrite en Java qui dit qu'en parcourant la liste, vous ne devez pas add() ouremove() jusqu'à ce que la collection prenne en charge l'itérateur à sécurité intégrée, par exemple CopyOnWriteArrayList , qui fonctionnent sur une copie de la liste plutôt que sur la liste d'origine.

Le principal problème avec cette erreur est qu'elle confond le développeur que la liste est modifiée par plusieurs threads et c'est pourquoi Java lance cette erreur, ce n'est pas vrai. La plupart du temps
ConcurrentModificationException vient même sans plusieurs threads modifiant la liste.

C'est un terme impropre, ne vous laissez pas berner par cela. bien qu'il semble naturel de penser qu'un autre thread essaie peut-être de modifier la collection en même temps, cela enfreint généralement la règle Java.

Dans cet article, j'expliquerai cette erreur et nous donnerons de nombreux exemples de code pour reproduire ce code même avec le seul thread et apprendre comment nous pouvons éviter une erreur de modification simultanée lors de la modification d'une ArrayList en Java.

Btw, si vous n'êtes pas familier avec les classes de collection, par exemple. ArrayList lui-même, vous devriez rejoindre un cours en ligne, par exemple
Java Basics :Learn to Code the Right Way on Udemy est un bon point de départ.

ConcurrentModificationException dans un seul thread

Il s'agit du premier exemple de reproduction de l'exception de modification simultanée en Java. Dans ce programme, nous itérons sur ArrayList en utilisant la boucle foreach améliorée et en supprimant des éléments sélectifs, par ex. un élément qui correspond à certaines conditions à l'aide de la méthode remove d'ArrayList.

Par exemple, dans le code ci-dessous, nous avons d'abord ajouté quelques bons livres de programmation, par ex. Programmation de Pearls, Clean Code, Code Complete dans ArrayList, puis suppression de tout élément contenant Code dans son titre.

package beginner;

import java.util.ArrayList;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       // Using forEach loop to iterate and removing 
       // element during iteration will throw 
       // ConcurrentModificationException in Java
       for(String book: listOfBooks){
           if(book.contains("Code"));{
               listOfBooks.remove(book);
           }
       }
   }

}
Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

Vous pouvez voir que cette erreur survient même si nous n'avons qu'un seul thread, le thread principal qui fonctionne avec ArrayList. Le ConcurrentModification l'erreur vient parce que nous n'utilisons pas Iterator, au lieu d'appeler simplement listOfBooks.remove() méthode.

Dans ce code, j'ai utilisé la boucle for améliorée de Java 1.5, vous devez savoir comment la boucle for améliorée fonctionne en Java.

La différence entre la boucle for et la boucle for améliorée est qu'elle utilise ultérieurement en interne un Iterator pour parcourir tous les éléments d'une collection. Pour une discussion plus approfondie, cliquez ici

Utiliser la boucle for classique et ArrayList.remove(index)

Voici un autre exemple de code intéressant de suppression d'éléments de ArrayList. Étonnamment, ce code ne lèvera pas ConcurrentModificationException lors de sa première exécution ? est-ce que tu sais pourquoi?

Eh bien, essayez-le avant de regarder l'explication après le code. C'est vraiment ce genre de détails mineurs sur le langage de programmation Java et le framework Collection, qui feront de vous un bon développeur, et vous aideront également à obtenir votre certification Java si vous vous y préparez.

package beginner;

import java.util.ArrayList;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       System.out.println("List before : " + listOfBooks);
       for(int i=0; i<listOfBooks.size(); i++){
           String book = listOfBooks.get(i);
           if(book.contains("Programming")){
               System.out.println("Removing " + book);
               listOfBooks.remove(i); // will throw CME
           }
       }
       System.out.println("List after : " + listOfBooks);
   }

}

Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
List after : [Clean Code, Effective Java, Code Complete]

Ce code ne renvoie pas ConcurrentModificationException parce qu'ici nous n'utilisons pas  Itérateur mais nous utilisons simplement la boucle for traditionnelle.

C'est l'itérateur qui lance ConcurrentModificationException , et non la méthode remove de ArrayList , donc vous ne voyez pas cette erreur dans le code ci-dessous.

Si vous regardez le code pour ArrayList.java , vous remarquerez qu'il existe une classe imbriquée qui a implémenté l'interface Iterator et sa méthode next() appelle checkForComodification() fonction qui vérifie réellement si ArrayList a été modifié au cours de l'itération ou non, si modCount ne correspond pas à expectedModCount puis il lance ConcurrentModificationException .

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

Ce type de questions est également très populaire sur Oracle Java Certification, par exemple. OCAJP (1z0-808) et OCPJP (1Z0-809), donc si vous vous préparez à ces examens, vous devriez connaître la réponse.

Voici l'extrait de code complet de la classe ArrayList.java pour votre référence rapide :

Utiliser Iterator mais la méthode remove de ArrayList

Voyons maintenant un autre exemple de code, où le programmeur Java pense qu'il a tout fait correctement mais obtient toujours l'exception de modification simultanée. Pouvez-vous repérer l'erreur? C'est très courant et j'ai souvent vu ce type de code sur les forums Java, StackOverflow et sur les groupes Facebook Java où ils ont demandé de résoudre le problème.

package beginner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           listOfBooks.remove(book);
       }
   }

}

Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

Le vrai problème avec ce code est que même si le code utilise Iterator pour dépasser ArrayList , il n'utilise pas vraiment le Iterator.remove() méthode pour supprimer l'élément. Il utilise simplement Iterator pour obtenir l'élément suivant mais appelle la méthode ArrayList.remove() pour supprimer l'élément.

Je sais, cela semble facile quand vous connaissez la raison, mais en temps réel, les programmeurs mettent souvent des heures à comprendre ce qui ne va pas. Alors, méfiez-vous de cela.

Au fait, si vous apprenez Java, je vous suggère de rejoindre  Complete Java Masterclass pour mieux apprendre Java et éviter ces erreurs courantes.

La bonne façon de supprimer un élément consiste à utiliser la méthode de suppression d'Iterator

Enfin, voici la bonne façon de supprimer un élément de ArrayList lors de l'itération. Dans cet exemple, nous avons utilisé Iterator pour itérer et supprimer l'élément. Le code est correct mais il a une sérieuse limitation, vous ne pouvez utiliser ce code que pour supprimer l'élément actuel. Vous ne pouvez supprimer aucun élément arbitraire de ArrayList en Java.

package beginner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class HelloWorldApp{

   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>();  
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
       
       System.out.println("List before : " + listOfBooks);
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           System.out.println("Removing " + book);
           iterator.remove();
       }
       System.out.println("List after : " + listOfBooks);
   }

}
Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
Removing Clean Code
Removing Effective Java
Removing Code Complete
List after : []

Le même comportement s'applique à ListIterator aussi bien. Je veux dire que vous pouvez remplacer Iterator par ListIterator et le code fonctionnera bien. Le ListIterator vous permettent également de naviguer dans les deux sens, c'est-à-dire vers l'avant et vers l'arrière.

Il s'agit de comment éviter le ConcurrentModificationException lors de la suppression d'éléments de ArrayList lors de l'itération . Vous pouvez utiliser la même technique pour éviter ConcurrentModificationException tout en supprimant des éléments de toutes les autres classes de collection qui ont un itérateur rapide, par exemple. Liste liée. Au fait, si vous débutez dans la programmation Java, rejoignez un bon cours complet comme Java Basics :Learn to Code the Right Way sur Udemy peut vous aider à apprendre Java mieux et plus rapidement.

AutresGuides de dépannage Java Tu peux aimer

Comment résoudre ArrayIndexOutOfBoundsException en Java ? (guide)
Comment résoudre NullPointerException en Java ? (guide)
Comment résoudre l'erreur "Le système ne trouve pas le chemin spécifié" ? (solution)
Comment résoudre NoClassDefFoundError lors de l'exécution d'un programme Java à partir d'une ligne de commande ? (solution)
Comment résoudre l'erreur "Aucune JVM trouvée, veuillez installer le JDK 64 bits" dans Android Studio ? (solution)
Comment traiter l'erreur SQLException "Aucun pilote approprié trouvé" dans JDBC et MySQL ? (guide)
Comment résoudre NumberFormatException en Java ? (guide)
Comment résoudre Minecraft – java.lang.UnsatisfiedLinkError :lwjgl64.dll :Accès refusé ? (solution)
Comment corriger java.lang.ArrayIndexOutOfBoundsException : 1 en Java ? (solution)
Comment réparer java.net.SocketException :le logiciel a provoqué l'abandon de la connexion :échec de la réception (correction)

Merci d'avoir lu ce tutoriel, si vous aimez ce tutoriel, partagez-le avec vos amis et collègues. Si vous avez une question ou une suggestion, veuillez laisser un commentaire.

Balise Java