Jak iterować i modyfikować zestawy Java?

80

Powiedzmy, że mam zestaw liczb całkowitych i chcę zwiększyć każdą liczbę całkowitą w zestawie. Jak bym to zrobił?

Czy mogę dodawać i usuwać elementy z zestawu podczas iteracji?

Czy musiałbym utworzyć nowy zestaw, do którego „skopiowałbym i zmodyfikował” elementy podczas iteracji oryginalnego zestawu?

EDYCJA: A co jeśli elementy zestawu są niezmienne?

Przyciski840
źródło

Odpowiedzi:

92

Możesz bezpiecznie usunąć z zestawu podczas iteracji za pomocą obiektu Iterator; próba zmodyfikowania zestawu za pomocą jego interfejsu API podczas iteracji spowoduje przerwanie iteratora. klasa Set udostępnia iterator za pomocą metody getIterator ().

jednak obiekty Integer są niezmienne; moja strategia polegałaby na iterowaniu zbioru i dodawaniu i + 1 do nowego zbioru tymczasowego dla każdej liczby całkowitej i. Po zakończeniu iteracji usuń wszystkie elementy z pierwotnego zestawu i dodaj wszystkie elementy nowego zestawu tymczasowego.

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
źródło
1
Czy nie byłoby łatwiej pozostawić oryginalny zestaw w odśmiecaczu i kontynuować używanie nowego zestawu? Albo zestaw tymczasowy zostanie zebrany, albo zestaw oryginalny, mój również po prostu zachowaj zestaw tymczasowy i nie zawracaj sobie głowy mutacją oryginału? Jednak w moim przypadku nie było to możliwe, ponieważ oryginalny zestaw był ostateczny, więc się zgodziłem.
Buttons840,
@JonathanWeatherhead Czy chodziło Ci o to, aby drugie słowo było cannotraczej niż can? A You cannot safely removeraczej niż You can safely remove? Wydaje się, że to sprzeczność w tym pierwszym akapicie.
Basil Bourque
@BasilBourque nope Miałem na myśli „może”. Metoda Iterator :: remove () jest bezpieczna, zgodnie z dokumentacją. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead
error: incompatible types: Object cannot be converted to Integer
alhelal
40

Możesz robić, co chcesz, jeśli użyjesz obiektu iteratora do przejrzenia elementów w swoim zestawie. Możesz je usunąć w drodze i wszystko w porządku. Jednak usunięcie ich w pętli for (albo „standardowej”, albo dla każdego rodzaju) spowoduje kłopoty:

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

Zgodnie z komentarzem @ mrgloom, oto więcej szczegółów wyjaśniających, dlaczego „zły” sposób opisany powyżej jest… cóż… zły:

Bez wchodzenia w zbyt wiele szczegółów na temat tego, jak Java implementuje to, na wysokim poziomie, możemy powiedzieć, że „zły” sposób jest zły, ponieważ jest jasno określony jako taki w dokumentacji Java:

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

zastrzegam między innymi, że (wyróżnienie moje):

" Na przykład, nie jest na ogół dopuszczalne jeden wątek zmodyfikować Collection a drugi gwint jest kolejno po nim. W ogólności, wyniki iteracji jest zdefiniowana w tych warunkach. Niektóre implementacje iterator (włącznie z wszystkimi zbierania General Purpose implementacje udostępnione przez środowisko JRE) mogą zgłosić ten wyjątek, jeśli zostanie wykryte to zachowanie „(...)

" Należy pamiętać, że ten wyjątek nie zawsze wskazują, że obiekt został jednocześnie modyfikowana przez inny wątek. Jeśli pojedyncze kwestie gwintów sekwencja metody wywołania, które naruszają umowę obiektu, obiekt może rzucić ten wyjątek. Na przykład, jeśli Wątek modyfikuje kolekcję bezpośrednio podczas iteracji po kolekcji z iteratorem działającym w trybie fail-fast, iterator zgłosi ten wyjątek. "

Aby przejść do szczegółów: obiekt, który może być użyty w pętli forEach, musi zaimplementować interfejs „java.lang.Iterable” ( tutaj javadoc ). Tworzy to Iterator (za pomocą metody „Iterator” znajdującej się w tym interfejsie), który jest tworzony na żądanie i będzie zawierał wewnętrznie odniesienie do obiektu Iterowalnego, z którego został utworzony. Jednak gdy w pętli forEach jest używany obiekt iterowalny, instancja tego iteratora jest ukryta dla użytkownika (nie można uzyskać do niego w żaden sposób dostępu samodzielnie).

To, w połączeniu z faktem, że Iterator jest dość stanowy, tj. Aby wykonać swoją magię i mieć spójne odpowiedzi na swoje metody "next" i "hasNext", potrzebuje, aby obiekt bazowy nie został zmieniony przez coś innego niż sam iterator podczas iteracji sprawia, że ​​zgłasza wyjątek, gdy tylko wykryje, że coś się zmieniło w obiekcie bazowym podczas iteracji po nim.

Java nazywa tę iterację „odporną na awarie”: tj. Istnieją pewne akcje, zwykle takie, które modyfikują instancję iterowalną (podczas gdy Iterator ją iteruje). Część „niepowodzenia” pojęcia „szybkiego niepowodzenia” odnosi się do zdolności Iteratora do wykrywania, kiedy takie „niepowodzenie” ma miejsce. „Szybka” część „fail-fast” (a, który moim zdaniem powinien być nazywany „best-effort-fast”), zakończy iteracji poprzez ConcurrentModificationException tak szybko, jak to może wykryć , że „nie” działanie ma zdarzyć.

Shivan Dragon
źródło
1
Czy możesz wyjaśnić, dlaczego zły sposób zawodzi?
mrgloom
@mrgloom Dodałem więcej szczegółów na temat, w zasadzie, dlaczego wyjątek ConcurrentModificationException może zostać zgłoszony, nawet jeśli na obiekcie
iterowalnym
W niektórych przypadkach otrzymałeś również java.lang.UnsupportedOperationException
Aguid
4

Nie lubię zbytnio semantyki iteratora, rozważ to jako opcję. Jest to również bezpieczniejsze, ponieważ publikujesz mniej informacji o swoim stanie wewnętrznym

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
źródło
0

Możesz stworzyć zmienną otokę pierwotnego int i stworzyć zbiór tych:

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

Oczywiście, jeśli używasz HashSet, powinieneś zaimplementować metodę hash, equals w swoim MutableInteger, ale to wykracza poza zakres tej odpowiedzi.

Savvas Dalkitsis
źródło