81 votes

Comment itérer et modifier les Java Sets ?

Disons que j'ai un ensemble de nombres entiers et que je veux incrémenter chaque nombre entier de l'ensemble. Comment dois-je m'y prendre ?

Suis-je autorisé à ajouter et à supprimer des éléments de l'ensemble tout en l'itérant ?

Devrais-je créer un nouvel ensemble dans lequel je "copierais et modifierais" les éléments, tout en itérant l'ensemble original ?

EDIT : Et si les éléments de l'ensemble sont immuables ?

94voto

Vous pouvez supprimer un ensemble en toute sécurité pendant l'itération avec un objet Iterator ; si vous tentez de modifier un ensemble par le biais de son API pendant l'itération, l'itérateur sera détruit. La classe Set fournit un itérateur par le biais de getIterator().

Cependant, les objets Integer sont immuables ; ma stratégie serait d'itérer dans l'ensemble et, pour chaque Integer i, d'ajouter i+1 à un nouvel ensemble temporaire. Lorsque vous avez terminé l'itération, supprimez tous les éléments de l'ensemble original et ajoutez tous les éléments du nouvel ensemble temporaire.

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);

41voto

Shivan Dragon Points 8626

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.

4voto

snovelli Points 52

Je n'aime pas beaucoup la sémantique de l'itérateur, veuillez considérer cette option. C'est aussi plus sûr car vous publiez moins de votre état interne.

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}

0voto

MarianP Points 1303

Tout d'abord, je pense qu'essayer de faire plusieurs choses à la fois est une mauvaise pratique en général et je vous suggère de réfléchir à ce que vous essayez d'accomplir.

C'est une bonne question théorique et d'après ce que j'ai compris, la CopyOnWriteArraySet mise en œuvre de java.util.Set satisfait à vos exigences particulières.

http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/CopyOnWriteArraySet.html

0voto

Instantsoup Points 6428

Vous pouvez modifier les membres d'un ensemble tout en itérant sur celui-ci. Tant que vous n'essayez pas de modifier la taille de l'ensemble, tout devrait bien se passer.

Vous pouvez supprimer des membres en utilisant la méthode iterator.remove(). Je ne pense pas que l'on puisse ajouter des éléments à un ensemble tout en itérant sur celui-ci. Si vous essayez d'ajouter ou de supprimer des membres d'une autre manière, vous rencontrerez une ConcurrentModificationException.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X