Éviter l’exception ConcurrentModificationException en Java

1. Introduction

Dans cet article, nous examinerons la classe ConcurrentModificationException .

Tout d’abord, nous allons expliquer son fonctionnement, puis le prouver en utilisant un test de déclenchement.

Enfin, nous allons essayer des solutions de contournement en utilisant des exemples pratiques.

2. Déclenchement d’une ConcurrentModificationException

La ConcurrentModificationException est essentiellement utilisée pour échouer rapidement lorsque quelque chose sur lequel nous itérons est modifié. Prouvons-le avec un simple test:

@Test(expected = ConcurrentModificationException.class)
public void whilstRemovingDuringIteration__shouldThrowException() throws InterruptedException {

    List<Integer> integers = newArrayList(1, 2, 3);

    for (Integer integer : integers) {
        integers.remove(1);
    }
}

Comme nous pouvons le constater, avant de terminer notre itération, nous retirons un élément. C’est ce qui déclenche l’exception.

3. Solutions

Parfois, nous pouvons réellement vouloir supprimer des éléments d’une collection lors de notre itération. Si tel est le cas, il existe des solutions.

3.1. Utilisation directe d’un itérateur

Une boucle for-each utilise un Iterator dans les coulisses mais est moins commentée. Cependant, si nous refacturions notre test précédent pour utiliser un Iterator, nous aurons accès à des méthodes supplémentaires, telles que remove () . Essayons d’utiliser cette méthode pour modifier notre liste à la place:

for (Iterator<Integer> iterator = integers.iterator(); iterator.hasNext();) {
    Integer integer = iterator.next();
    if(integer == 2) {
        iterator.remove();
    }
}

Maintenant, nous allons remarquer qu’il n’ya pas d’exception. La raison en est que la méthode remove () ne provoque pas d’exception ConcurrentModificationException. Il est sûr d’appeler pendant l’itération.

3.2. Ne pas enlever pendant l’itération

Si nous voulons garder notre boucle for-each , alors nous le pouvons. C’est juste que nous devons attendre après une itération avant de supprimer les éléments. Essayons cela en ajoutant ce que nous voulons supprimer à une liste toRemove au fur et à mesure de notre itération:

List<Integer> integers = newArrayList(1, 2, 3);
List<Integer> toRemove = newArrayList();

for (Integer integer : integers) {
    if(integer == 2) {
        toRemove.add(integer);
    }
}
integers.removeAll(toRemove);

assertThat(integers).containsExactly(1, 3);

C’est un autre moyen efficace de résoudre le problème.

3.3. Utiliser removeIf ()

Java 8 a introduit la méthode removeIf () dans l’interface Collection .

Cela signifie que si nous travaillons avec, nous pouvons utiliser des idées de programmation fonctionnelle pour obtenir à nouveau les mêmes résultats:

List<Integer> integers = newArrayList(1, 2, 3);

integers.removeIf(i -> i == 2);

assertThat(integers).containsExactly(1, 3);

Ce style déclaratif nous offre le moins de verbosité possible. Cependant, selon le cas d’utilisation, d’autres méthodes pourraient s’avérer plus pratiques.

3.4. Filtrage à l’aide de flux

Lorsque vous plongez dans le monde de la programmation fonctionnelle/déclarative, vous pouvez oublier les collections en mutation, mais plutôt vous concentrer sur les éléments qui doivent être réellement traités:

Collection<Integer> integers = newArrayList(1, 2, 3);

List<String> collected = integers
  .stream()
  .filter(i -> i != 2)
  .map(Object::toString)
  .collect(toList());

assertThat(collected).containsExactly("1", "3");

Nous avons effectué l’inverse de notre exemple précédent en fournissant un prédicat permettant de déterminer les éléments à inclure et non à exclure. L’avantage est que nous pouvons enchaîner d’autres fonctions parallèlement à la suppression. Dans l’exemple, nous utilisons une map () fonctionnelle, mais pourrions utiliser encore plus d’opérations si nous le souhaitions.

4. Conclusion

Dans cet article, nous avons présenté les problèmes que vous pouvez rencontrer si vous supprimez des éléments d’une collection lors d’une itération, ainsi que des solutions pour résoudre le problème.

La mise en œuvre de ces exemples est disponible à l’adresse over sur GitHub . Ceci est un projet Maven, il devrait donc être facile à exécuter tel quel.