Wie iteriere und ändere ich Java-Sets?

80

Angenommen, ich habe eine Reihe von Ganzzahlen und möchte jede Ganzzahl in der Menge erhöhen. Wie würde ich das machen?

Darf ich während des Iterierens Elemente zum Set hinzufügen und daraus entfernen?

Müsste ich einen neuen Satz erstellen, in den ich die Elemente "kopieren und ändern" würde, während ich den ursprünglichen Satz iteriere?

EDIT: Was ist, wenn die Elemente des Sets unveränderlich sind?

Buttons840
quelle

Antworten:

92

Sie können während der Iteration mit einem Iterator-Objekt sicher aus einem Satz entfernen. Wenn Sie versuchen, einen Satz während der Iteration über seine API zu ändern, wird der Iterator unterbrochen. Die Set-Klasse stellt über getIterator () einen Iterator bereit.

Integer-Objekte sind jedoch unveränderlich. Meine Strategie wäre, die Menge zu durchlaufen und für jede ganze Zahl i i + 1 zu einer neuen temporären Menge hinzuzufügen. Wenn Sie mit dem Iterieren fertig sind, entfernen Sie alle Elemente aus dem ursprünglichen Satz und fügen Sie alle Elemente des neuen temporären Satzes hinzu.

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);
Jonathan Weatherhead
quelle
1
Wäre es nicht einfacher, das ursprüngliche Set dem Garbage Collector zu überlassen und das neue Set weiter zu verwenden? Entweder wird das temporäre Set gesammelt oder das ursprüngliche Set, meins behält auch nur das temporäre Set und macht sich nicht die Mühe, das Original zu mutieren? Dies war jedoch in meinem Fall nicht möglich, da das ursprüngliche Set endgültig war, also akzeptierte ich.
Buttons840
@ JonathanWeatherhead Meinten Sie das zweite Wort cannoteher als can? Wie in, You cannot safely removeanstatt You can safely remove? Scheint ein Widerspruch in diesem ersten Absatz zu sein.
Basil Bourque
@BasilBourque nein, ich meinte "kann". Die Iterator :: remove () -Methode ist sicher, wie in den Dokumenten angegeben. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead
error: incompatible types: Object cannot be converted to Integer
Alhelal
40

Sie können tun, was Sie wollen, wenn Sie ein Iteratorobjekt verwenden, um die Elemente in Ihrem Satz zu durchlaufen. Sie können sie unterwegs entfernen und es ist in Ordnung. Wenn Sie sie jedoch in einer for-Schleife entfernen (entweder "Standard" für jede Art), werden Sie in Schwierigkeiten geraten:

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

Nach dem Kommentar von @ mrgloom gibt es hier weitere Details, warum der oben beschriebene "schlechte" Weg ist, na ja ... schlecht:

Ohne auf zu viele Details darüber einzugehen, wie Java dies implementiert, können wir auf hoher Ebene sagen, dass der "schlechte" Weg schlecht ist, weil er in den Java-Dokumenten eindeutig als solcher festgelegt ist:

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

unter anderem festlegen, dass (Hervorhebung von mir):

" Beispielsweise ist es im Allgemeinen nicht zulässig, dass ein Thread eine Sammlung ändert, während ein anderer Thread darüber iteriert. Im Allgemeinen sind die Ergebnisse der Iteration unter diesen Umständen nicht definiert. Einige Iterator-Implementierungen (einschließlich derjenigen der gesamten Allzweck-Sammlung) Implementierungen, die von der JRE bereitgestellt werden) können diese Ausnahme auslösen, wenn dieses Verhalten erkannt wird "(...)

" Beachten Sie, dass diese Ausnahme nicht immer anzeigt, dass ein Objekt gleichzeitig von einem anderen Thread geändert wurde. Wenn ein einzelner Thread eine Folge von Methodenaufrufen ausgibt, die gegen den Vertrag eines Objekts verstoßen, kann das Objekt diese Ausnahme auslösen. Beispiel: if Ein Thread ändert eine Sammlung direkt, während er mit einem ausfallsicheren Iterator über die Sammlung iteriert. Der Iterator löst diese Ausnahme aus. "

Um näher auf Details einzugehen: Ein Objekt, das in einer forEach-Schleife verwendet werden kann, muss die Schnittstelle "java.lang.Iterable" ( hier javadoc ) implementieren . Dies erzeugt einen Iterator (über die in dieser Schnittstelle gefundene "Iterator" -Methode), der bei Bedarf instanziiert wird und intern einen Verweis auf das Iterable-Objekt enthält, aus dem er erstellt wurde. Wenn jedoch ein iterierbares Objekt in einer forEach-Schleife verwendet wird, ist die Instanz dieses Iterators für den Benutzer ausgeblendet (Sie können in keiner Weise selbst darauf zugreifen).

Dies, zusammen mit der Tatsache, dass ein Iterator ziemlich zustandsbehaftet ist, dh um seine Magie zu entfalten und kohärente Antworten für seine "next" - und "hasNext" -Methoden zu erhalten, muss das Hintergrundobjekt nicht durch etwas anderes als den Iterator selbst geändert werden Während der Iteration wird eine Ausnahme ausgelöst, sobald festgestellt wird, dass sich etwas im Hintergrundobjekt geändert hat, während es darüber iteriert.

Java nennt diese "Fail-Fast" -Iteration: dh es gibt einige Aktionen, normalerweise solche, die eine Iterable-Instanz ändern (während ein Iterator darüber iteriert). Der Teil "Fehler" des Begriffs "Fehler schnell" bezieht sich auf die Fähigkeit eines Iterators, zu erkennen, wann solche "Fehler" -Aktionen auftreten. Der "schnelle" Teil des "Fail-Fast" (und meiner Meinung nach "Best-Effort-Fast") beendet die Iteration über ConcurrentModificationException , sobald festgestellt werden kann, dass eine "Fail" -Aktion vorliegt geschehen.

Shivan Drache
quelle
1
Können Sie erklären, warum ein schlechter Weg fehlschlägt?
Mrgloom
@ Mrgloom Ich habe im Grunde mehr Details hinzugefügt, warum eine ConcurrentModificationException ausgelöst werden kann, selbst wenn nur ein Thread auf einem Iterable-Objekt ausgeführt wird
Shivan Dragon
Für einige Fälle haben Sie auch java.lang.UnsupportedOperationException
Aguid
4

Ich mag die Semantik des Iterators nicht sehr, bitte betrachten Sie dies als Option. Es ist auch sicherer, wenn Sie weniger von Ihrem internen Status veröffentlichen

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;

}
Snovelli
quelle
0

Sie können einen veränderlichen Wrapper des primitiven int erstellen und eine Menge davon erstellen:

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

Wenn Sie ein HashSet verwenden, sollten Sie natürlich die Methode hash, equals in Ihrer MutableInteger implementieren, aber das liegt außerhalb des Rahmens dieser Antwort.

Savvas Dalkitsis
quelle