it-swarm.it

Come posso iterare e modificare Java Sets?

Diciamo che ho un set di numeri interi e voglio incrementare ogni numero intero nel set. Come lo farei?

Sono autorizzato ad aggiungere e rimuovere elementi dal set durante l'iterazione?

Dovrei creare un nuovo set in cui "copiare e modificare" gli elementi, mentre sto ripetendo il set originale?

EDIT: Cosa succede se gli elementi del set sono immutabili?

76
Buttons840

È possibile rimuovere in modo sicuro da un set durante l'iterazione con un oggetto Iterator; il tentativo di modificare un set tramite la sua API durante l'iterazione interromperà l'iteratore. la classe Set fornisce un iteratore tramite getIterator ().

tuttavia, gli oggetti Integer sono immutabili; la mia strategia sarebbe quella di iterare attraverso il set e per ogni intero i, aggiungere i + 1 a un nuovo set temporaneo. Al termine dell'iterazione, rimuovere tutti gli elementi dal set originale e aggiungere tutti gli elementi del nuovo set temporaneo.

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);
89

Puoi fare quello che vuoi se usi un oggetto iteratore per andare oltre gli elementi nel tuo set. Puoi rimuoverli mentre sei in movimento e va bene. Tuttavia, rimuoverli mentre si è in un ciclo for (o "standard", del tipo per ogni tipo) ti metterà nei guai:

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);
        } 
    }

Secondo il commento di @ mrgloom, qui ci sono maggiori dettagli sul perché il modo "cattivo" sopra descritto è, beh ... cattivo:

Senza entrare troppo nei dettagli su come Java lo implementa, ad alto livello, possiamo dire che il modo "cattivo" è cattivo perché è chiaramente definito come tale nel Java docs:

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

stipulare, tra l'altro, che (sottolineatura mia):

" Ad esempio, non è generalmente consentito per un thread modificare una raccolta mentre un altro thread sta iterando su di esso. In generale, i risultati dell'iterazione non sono definiti in queste circostanze. Alcune implementazioni di Iterator (incluse quelle di tutte le implementazioni di raccolta per scopi generici fornite da JRE) possono scegliere di gettare questa eccezione se viene rilevato questo comportamento "(...)

" Questa eccezione non indica sempre che un oggetto è stato modificato contemporaneamente da un thread diverso. Se un singolo thread emette una sequenza di invocazioni di metodo che viola il contratto di un oggetto, l'oggetto può generare questa eccezione. Ad esempio, se un thread modifica direttamente una raccolta mentre sta iterando sulla raccolta con un iteratore fail-fast, l'iteratore genererà questa eccezione. "

Per ulteriori dettagli: un oggetto che può essere utilizzato in un ciclo forEach deve implementare l'interfaccia "Java.lang.Iterable" (javadoc qui ). Questo produce un Iterator (tramite il metodo "Iterator" trovato in questa interfaccia), che viene istanziato su richiesta e conterrà internamente un riferimento all'oggetto Iterable da cui è stato creato. Tuttavia, quando un oggetto Iterable viene utilizzato in un ciclo forEach, l'istanza di questo iteratore viene nascosta all'utente (non è possibile accedervi in ​​alcun modo).

Questo, insieme al fatto che un Iteratore è piuttosto statoful, cioè per fare la sua magia e avere risposte coerenti per i suoi metodi "next" e "hasNext" ha bisogno che l'oggetto di supporto non sia cambiato da qualcos'altro rispetto allo stesso iteratore mentre sta iterando, lo fa in modo che genererà un'eccezione non appena rileva che qualcosa è cambiato nell'oggetto di supporto mentre sta iterando su di esso.

Java chiama questa iterazione "fail-fast": cioè ci sono alcune azioni, di solito quelle che modificano un'istanza Iterable (mentre un Iterator sta iterando su di essa). La parte "fail" dell'idea "fail-fast" si riferisce alla capacità di un Iteratore di rilevare quando si verificano tali azioni "fail". La parte "veloce" di "fail-fast" (e, che a mio avviso dovrebbe essere chiamata "best-effort-fast"), interromperà l'iterazione tramite ConcurrentModificationException non appena poiché è in grado di rilevare che si è verificata un'azione "non riuscita".

39
Shivan Dragon

Non mi piace molto la semantica dell'iteratore, ti preghiamo di considerare questa opzione. È anche più sicuro se pubblichi meno del tuo stato interno

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

In primo luogo, credo che provare a fare diverse cose contemporaneamente sia una cattiva pratica in generale e ti suggerisco di pensare a ciò che stai cercando di ottenere.

Serve come una buona domanda teorica e da quello che raccolgo l'implementazione CopyOnWriteArraySet dell'interfaccia Java.util.Set soddisfa i tuoi requisiti piuttosto speciali.

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

0
MarianP

È possibile creare un wrapper mutabile dell'int primitivo e creare un insieme di quelli:

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);
        }
    }
}

Ovviamente se stai usando un HashSet dovresti implementare l'hash, metodo uguale nel tuo MutableInteger ma questo è al di fuori dell'ambito di questa risposta.

0
Savvas Dalkitsis