Vous pouvez faire ce que vous voulez si vous utilisez un objet itérateur pour passer en revue les éléments de votre ensemble. Vous pouvez les supprimer en cours de route et c'est correct. Cependant, si vous les supprimez dans une boucle for (soit "standard", soit du type for each), vous aurez des problèmes :
Set<Integer> set = new TreeSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
//good way:
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
Integer setElement = iterator.next();
if(setElement==2) {
iterator.remove();
}
}
//bad way:
for(Integer setElement:set) {
if(setElement==2) {
//might work or might throw exception, Java calls it indefined behaviour:
set.remove(setElement);
}
}
Conformément au commentaire de @mrgloom, voici plus de détails sur la raison pour laquelle la "mauvaise" façon décrite ci-dessus est, eh bien... mauvaise :
Sans entrer dans les détails de l'implémentation de Java, nous pouvons dire que la "mauvaise" méthode est mauvaise parce qu'elle est clairement stipulée comme telle dans la documentation de Java :
https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html
stipulent, entre autres, que (c'est moi qui souligne) :
" Par exemple, il n'est généralement pas permis à un thread de modifier une collection pendant qu'un autre thread la parcourt. Sur En général, les résultats de l'itération sont indéfinis dans le cadre de ces circonstances. Certaines implémentations d'Iterator (y compris celles de toutes les les implémentations de collection d'usage général fournies par le JRE) peuvent choisir de lancer cette exception si ce comportement est détecté" (...)
" 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'invocations de méthodes qui viole le contrat contrat d'un objet, l'objet peut lever cette exception. Pour Par exemple, si un thread modifie une collection directement pendant qu'il alors qu'il itère sur la collection avec un itérateur rapide, l'itérateur lèvera cette exception".
Pour entrer dans les détails : un objet qui peut être utilisé dans une boucle forEach doit implémenter l'interface "java.lang.Iterable" (javadoc aquí ). On obtient ainsi un Itérateur (via la méthode "Iterator" de cette interface), qui est instancié à la demande et contient en interne une référence à l'objet Iterable à partir duquel il a été créé. Cependant, lorsqu'un objet Iterable est utilisé dans une boucle forEach, l'instance de cet itérateur est cachée à l'utilisateur (vous ne pouvez pas y accéder vous-même de quelque manière que ce soit).
Ceci, ajouté au fait qu'un Iterator est plutôt stateful, c'est-à-dire que pour faire sa magie et avoir des réponses cohérentes pour ses méthodes "next" et "hasNext", il a besoin que l'objet de base ne soit pas modifié par quelque chose d'autre que l'itérateur lui-même pendant qu'il itère, fait qu'il lèvera une exception dès qu'il détectera que quelque chose a changé dans l'objet de base pendant qu'il itère sur lui.
Java appelle cela l'itération "fail-fast" : c'est-à-dire qu'il existe certaines actions, généralement celles qui modifient une instance d'Iterable (alors qu'un Iterator est en train de l'itérer). La partie "fail" de la notion "fail-fast" fait référence à la capacité d'un Iterator à détecter quand de telles actions "fail" se produisent. La partie "rapide" de la notion "fail-fast" (qui, à mon avis, devrait être appelée "best-effort-fast"), mettra fin à l'itération via ConcurrentModificationException. dès que possible comme il peut détecter qu'une action "fail" s'est produite.