Aikido

Pourquoi utiliser des modèles sûrs lors de la suppression d'éléments de collections

Lisibilité

Règle
Utilisation des méthodes lorsque retirer des collections.
Modifier une collection tout en l'itération sur celle-ci souvent provoque des bugs.

Langues prises en charge : PY, Java, C/C++, C#, 
Swift/Objective-C, Ruby, PHP, Kotlin, Go,
Scala, Rust, Groovy, Dart, Julia, Elixit, 
Erlang, Clojure, OCaml, Lua

Introduction

La suppression d'éléments d'une collection pendant son itération provoque des exceptions de modification concurrente en Java et un comportement imprévisible en C#. L'itérateur maintient un pointeur interne qui devient invalide lorsque la collection sous-jacente est modifiée. Cela peut entraîner des éléments ignorés, des plantages ou des boucles infinies, selon le type de collection et le modèle de suppression utilisé.

Pourquoi c'est important

Stabilité du système : Les exceptions de modification concurrente font planter l'application immédiatement. En production, cela se traduit par des requêtes abandonnées et une indisponibilité du service. L'exception survient souvent dans des cas limites avec des données spécifiques, ce qui la rend difficile à détecter lors des tests.

Intégrité des données : Lorsque la logique de suppression échoue en cours d'itération, la collection est laissée dans un état partiellement modifié. Certains éléments sont supprimés tandis que d'autres qui auraient dû l'être restent. Cela crée des données incohérentes qui affectent la logique en aval.

Complexité du débogage : Les bugs de modification concurrente dépendent du timing et peuvent ne se manifester qu'avec certaines combinaisons de données. Ils sont difficiles à reproduire de manière cohérente, ce qui les rend ardus à déboguer et à corriger de manière fiable.

Exemples de code

❌ Non conforme :

List<User> users = getUserList();
for (User user : users) {
    if (!user.isActive()) {
        users.remove(user); // ConcurrentModificationException
    }
}

Pourquoi c'est incorrect : Suppression de utilisateurs lorsque l'itération avec une boucle for améliorée provoque ConcurrentModificationException. L'itérateur détecte que la collection a été modifiée en dehors de l'itérateur et lève immédiatement une exception. Tout utilisateur actif après le premier inactif n'est jamais traité.

✅ Conforme :

List<User> users = getUserList();
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
    User user = iterator.next();
    if (!user.isActive()) {
        iterator.remove(); // Safe removal through iterator
    }
}

Pourquoi c'est important : Utilisation iterator.remove() supprime les éléments en toute sécurité pendant l'itération. L'itérateur maintient un état cohérent et continue de traiter les éléments restants. Tous les utilisateurs inactifs sont correctement supprimés sans exception.

Conclusion

Utilisez les itérateurs remove() méthode pour une suppression sûre pendant l'itération. Alternativement, utilisez des flux avec filter() pour créer de nouvelles collections ou removeIf() pour la suppression en masse. Ne jamais appeler la collection de remove() directement lors de l'itération.

FAQ

Des questions ?

Qu'en est-il de l'utilisation de boucles for classiques avec un index ?

L'itération à rebours avec un index fonctionne : for (int i = list.size() - 1; i >= 0; i--). La suppression d'éléments décale les éléments suivants, mais l'itération à rebours évite de sauter des éléments. Cependant, `iterator.remove()` ou `removeIf()` sont plus clairs et moins sujets aux erreurs.

Puis-je utiliser removeIf de Java 8+ à la place ?

Oui, users.removeIf(user -> !user.isActive()) est l'approche moderne préférée. Elle est plus concise et gère l'itération en toute sécurité en interne. Utilisez removeIf() lors de la suppression basée sur un prédicat, les streams pour les transformations, et les méthodes d'itérateur lorsque la logique de suppression est complexe.

Cela s'applique-t-il à tous les types de collections ?

Oui, ArrayList, HashSet, HashMap et la plupart des collections lancent une ConcurrentModificationException lorsqu'elles sont modifiées pendant l'itération. Les collections thread-safe comme ConcurrentHashMap autorisent la modification mais ont des sémantiques différentes. Vérifiez toujours la documentation de la collection pour connaître les règles de modification.

Qu'en est-il des collections C# ?

C# lève une InvalidOperationException lorsque les collections sont modifiées pendant l'itération. Utilisez ToList() pour créer une copie avant d'itérer : foreach (var user in users.ToList()) puis supprimez de l'original. Ou utilisez LINQ : users = users.Where(u => u.IsActive).ToList().

Comment supprimer plusieurs éléments efficacement ?

Utilisez `removeIf()` pour une collection unique ou les streams pour un filtrage complexe : users = users.stream().filter(User::isActive).collect(Collectors.toList()). Ces approches sont optimisées pour les opérations en masse et sont plus efficaces que la suppression d'éléments un par un dans une boucle.

Sécurisez-vous maintenant.

Sécuriser votre code, votre cloud et votre runtime dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Pas de carte de crédit requise | Résultats du scan en 32 secondes.