web-dev-qa-db-fra.com

Comment puis-je itérer et modifier les ensembles Java?

Supposons que j'ai un ensemble d'entiers et que je souhaite incrémenter chaque entier du jeu. Comment je ferais ça?

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

Aurais-je besoin de créer un nouvel ensemble dans lequel je "copierais" et modifierais les éléments, pendant que j'itérerais l'ensemble original?

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

77
Buttons840

Vous pouvez supprimer en toute sécurité d'un ensemble lors de l'itération avec un objet Iterator; Toute tentative de modification d'un ensemble via son API lors d'une itération annule l'itérateur. la classe Set fournit un itérateur via getIterator ().

cependant, les objets Integer sont immuables. Ma stratégie consisterait à parcourir l'ensemble et en ajoutant i + 1 à chaque nouvel ensemble temporaire pour chaque entier i. Lorsque vous avez terminé l'itération, supprimez tous les éléments de l'ensemble d'origine 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);
90

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 sur la route et c'est bon. Cependant, les supprimer alors que vous êtes dans une boucle for (soit "standard", soit pour chaque type) vous causera 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);
        } 
    }

Selon le 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 trop dans les détails sur la façon dont Java l'implémente, on peut dire à haut niveau que la "mauvaise" façon de faire est mauvaise car elle est clairement stipulée comme telle dans le Java docs:

https://docs.Oracle.com/javase/8/docs/api/Java/util/ConcurrentModificationException.html

stipuler, entre autres, que (c'est moi qui souligne):

" Par exemple, il n'est généralement pas permis à un thread de modifier une collection alors qu'un autre thread effectue une itération dessus. En général, les résultats de l'itération Certaines implémentations d'Iterator (y compris celles de toutes les implémentations de collection à usage général fournies par JRE) peuvent choisir de lever 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'appels de méthode qui ne respecte pas le contrat d'un objet, l'objet Cette exception peut provoquer cette exception. Par exemple, si un fil modifie directement une collection alors qu’il itère sur la collection avec un itérateur à la procédure rapide, l’itérateur lève cette exception. "

Pour aller plus dans les détails: un objet qui peut être utilisé dans une boucle forEach doit implémenter l'interface "Java.lang.Iterable" (javadoc here ). Cela produit un Iterator (via la méthode "Iterator" trouvée dans cette interface), qui est instancié à la demande, et contient en interne une référence à l'objet Iterable à partir duquel elle a été créée. Cependant, lorsqu'un objet Iterable est utilisé dans une boucle forEach, l'instance de cet itérateur est masquée pour l'utilisateur (vous ne pouvez y accéder d'aucune façon).

Ceci, associé au fait qu’un itérateur est assez dynamique, c’est-à-dire pour pouvoir faire sa magie et avoir des réponses cohérentes pour ses méthodes "next" et "hasNext", il est nécessaire que l’objet support ne soit pas modifié par autre chose que l’itérateur lui-même. pendant qu'il est itératif, fait en sorte qu'il lève une exception dès qu'il détecte que quelque chose a changé dans l'objet de support pendant qu'il itère dessus.

Java appelle cette itération "fail-fast": c’est-à-dire qu’il existe certaines actions, généralement celles qui modifient une instance Iterable (pendant qu’un itérateur l’itère). La partie "échec" de la notion "échec rapide" fait référence à la capacité d'un itérateur à détecter le moment où de telles actions "échouent". La partie "rapide" de "fail-fast" (et, qui, à mon avis, devrait s'appeler "best-effort-fast"), mettra fin à l'itération via ConcurrentModificationException dès que car il peut détecter qu’une action "échec" a eu lieu.

39
Shivan Dragon

Je n'aime pas beaucoup la sémantique de l'itérateur, considérez ceci comme une option. Il est également plus sûr que 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;

}
4
snovelli

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'atteindre.

Cependant, c’est une bonne question théorique et, d’après ce que j’ai compris, l’interface CopyOnWriteArraySet de Java.util.Set répond à vos exigences particulières.

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

0
MarianP

Vous pouvez créer un wrapper modifiable de la primitive int et créer un ensemble de ceux-ci:

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

Bien sûr, si vous utilisez un hachage, vous devez implémenter la méthode hash, equals dans votre MutableInteger, mais cela sort du cadre de cette réponse.

0
Savvas Dalkitsis