Niezmienne kolekcje Java

115

Z dokumentacji Java 1.6 Collection Framework :

Kolekcje, które nie obsługują żadnych operacji modyfikacji (takich jak add, removei clear) są określane jako niemodyfikowalne . […] Kolekcje, które dodatkowo gwarantują, że żadna zmiana w obiekcie Collection nigdy nie będzie widoczna, nazywane są niezmiennymi .

Drugie kryterium trochę mnie dezorientuje. Biorąc pod uwagę, że pierwsza kolekcja jest niemodyfikowalna i przy założeniu, że oryginalne odniesienie do kolekcji zostało usunięte, jakie zmiany są wymienione w drugiej linii? Czy to odnosi się do zmian w elementach znajdujących się w kolekcji, czyli stanu elementów?

Drugie pytanie:
Aby kolekcja była niezmienna, w jaki sposób można zapewnić dodatkowe określone gwarancje? Jeśli stan elementu w kolekcji jest aktualizowany przez wątek, czy dla niezmienności wystarczy, że te aktualizacje w stanie nie są widoczne w wątku zawierającym niezmienną kolekcję?

Aby kolekcja była niezmienna, w jaki sposób można zapewnić dodatkowe określone gwarancje?

Bhaskar
źródło
Ogólnie (zwłaszcza w językach funkcjonalnych) niezmienna (czyli trwała) kolekcja może zmienić się w tym sensie, że można uzyskać nowy stan tej kolekcji, ale jednocześnie stary stan będzie nadal dostępny z innych linków. Na przykład newCol = oldCol.add("element")utworzy nową kolekcję, która jest kopią starej z 1 elementem więcej, a wszystkie odwołania do oldColbędą nadal wskazywały na tę samą niezmienioną starą kolekcję.
ffriend

Odpowiedzi:

154

Kolekcje niemodyfikowalne to zazwyczaj widoki tylko do odczytu (opakowania) innych kolekcji. Nie możesz ich dodawać, usuwać ani czyścić, ale podstawowa kolekcja może się zmienić.

Niezmiennych kolekcji nie można w ogóle zmienić - nie zawijają one innej kolekcji - mają własne elementy.

Oto cytat z guawy ImmutableList

W przeciwieństwie Collections.unmodifiableList(java.util.List<? extends T>)do widoku osobnego zbioru, który wciąż może się zmieniać, instancja ImmutableListzawiera własne dane prywatne i nigdy się nie zmieni.

Zasadniczo więc, aby uzyskać niezmienną kolekcję z mutowalnej, musisz skopiować jej elementy do nowej kolekcji i zabronić wszystkich operacji.

Bozho
źródło
@Bhaskar - zobacz mój ostatni akapit.
Bozho
1
jeśli kolekcja, która jest zapakowana w inną kolekcję niemodyfikowalną, nie ma żadnych innych odniesień do niej, to czy zachowanie kolekcji niemodyfikowalnej jest dokładnie takie samo jak niezmiennej kolekcji?
Bhaskar
1
@Bozho, jeśli nie podano odniesienia do kolekcji zapasowej i podano tylko odwołanie do opakowania, którego nie można modyfikować, nie można go zmienić. Dlaczego więc mówisz „Nie możesz być pewien”? Czy wskazuje na scenariusz, w którym jakiś wątek lub w jakiś sposób zostaje zmodyfikowany, ponieważ kolekcja zapasowa jest modyfikowalna?
AKS
@AKS: Gdy kolekcja jest opakowana unmodifiableList, kod, który otrzymuje tylko odniesienie do tej listy, nie będzie mógł jej zmodyfikować, ale każdy kod, który miał odniesienie do oryginalnej listy i mógł zmodyfikować ją przed utworzeniem opakowania, nadal będzie móc to zrobić później. Jeśli kod, który utworzył oryginalną listę, wie, co się stało z każdym odniesieniem, które kiedykolwiek do niej istniało, i wie, że żadne z nich nie wpadnie w ręce kodu, który mógłby zmodyfikować listę, to może wiedzieć, że lista nigdy nie będzie zmodyfikowany. Jeśli jednak odniesienie zostało otrzymane z zewnętrznego kodu ...
supercat
... nie ma sposobu, aby unmodifiableListani żaden kod, który go używa, wiedzieli, czy lub jak opakowana kolekcja może się zmienić.
supercat
86

Różnica polega na tym, że nie można mieć odwołania do niezmiennej kolekcji, która umożliwia zmiany. Kolekcje niemodyfikowalne są niemodyfikowalne poprzez to odniesienie nie mogą być , ale jakiś inny obiekt może wskazywać na te same dane, za pomocą których można je zmienić.

na przykład

List<String> strings = new ArrayList<String>();
List<String> unmodifiable = Collections.unmodifiableList(strings);
unmodifiable.add("New string"); // will fail at runtime
strings.add("Aha!"); // will succeed
System.out.println(unmodifiable);
Simon Nickerson
źródło
1
jakie różne przyczyny, jeśli istnieją, mogą przynieść zmianę w kolekcji, której nie można modyfikować, pod warunkiem, że oryginalne odniesienie do kolekcji nie jest używane do modyfikowania podstawowej kolekcji?
Bhaskar
21
Collection<String> c1 = new ArrayList<String>();
c1.add("foo");
Collection<String> c2 = Collections.unmodifiableList(c1);

c1jest zmienny (tj. ani niemodyfikowalny, ani niezmienny ).
c2jest niezmienne : nie może być zmieniana sama, ale jeśli później zmienić c1wtedy , że zmiany będą widoczne wc2 .

Dzieje się tak, ponieważ c2jest po prostu opakowaniem c1i nie jest tak naprawdę niezależną kopią. Guava zapewnia ImmutableListinterfejs i niektóre implementacje. Działają one poprzez faktyczne tworzenie kopii danych wejściowych (chyba że dane wejściowe są same w sobie niezmienną kolekcją).

Odnośnie drugiego pytania:

Zmienność / niezmienność kolekcji nie zależy od zmienności / niezmienności obiektów w niej zawartych. Modyfikacja obiektu zawartego w kolekcji nie liczy się jako „modyfikacja kolekcji” w tym opisie. Oczywiście, jeśli potrzebujesz niezmiennej kolekcji, zwykle chcesz, aby zawierała niezmienne obiekty.

Joachim Sauer
źródło
2
@John: nie, nie jest. Przynajmniej nie zgodnie z definicją przytoczoną w PO.
Joachim Sauer
@JohnVint, naprawdę? Nie sądzę, bo używając c1 nadal mogę do niego dodawać elementy, a ten dodatek będzie widoczny dla c2. Czy to nie znaczy, że nie jest niezmienna?
Bhaskar
Cóż, myślę, że jeśli c1 może uciec, to nie byłoby to niezmienne, ale niezmienność odnosi się również do zawartości w kolekcji. Bardziej odnosiłem się do niemodyfikowalnego zbioru java.util.Date's
John Vint,
1
@Vint: „jeśli c1nie ucieka” nie jest kwestią wyróżnioną w dowolnym miejscu specyfikacji. Moim zdaniem, jeśli pan może korzystać ze zbiorów w sposób, który sprawia, że nie niezmienne, to należy zawsze uważają, że zakaz niezmienne.
Joachim Sauer
1
Zacytuję definicję: „Kolekcje, które dodatkowo gwarantują …” (wyróżnienie moje). Collection.unmodifiableList() nie może tego zagwarantować, ponieważ nie może zagwarantować, że jego argument nie ucieknie. Guawa ImmutableList.of zawsze tworzy niezmienne List, nawet jeśli pozwolisz uciec jego argumentom.
Joachim Sauer
17

Teraz java 9 ma fabryczne metody dla Immutable List, Set, Map i Map.Entry.

W Javie SE 8 i wcześniejszych wersjach możemy używać metod narzędziowych klasy Collections, takich jak unmodifiableXXX, do tworzenia obiektów Immutable Collection.

Jednak te metody Collections.unmodifiableXXX są bardzo żmudne i rozwlekłe. Aby przezwyciężyć te niedociągnięcia, firma Oracle dodała kilka metod narzędziowych do interfejsów List, Set i Map.

Teraz w java 9: ​​- Interfejsy List i Set mają metody „of ()” do tworzenia pustych lub niepustych obiektów Immutable List lub Set, jak pokazano poniżej:

Przykład pustej listy

List immutableList = List.of();

Przykład niepustej listy

List immutableList = List.of("one","two","three");
Opster Elasticsearch Pro-Vijay
źródło
2
A w Javie 10 List.copyOfi Set.copyOfzostały dodane, które pozwalają na tworzenie niemodyfikowalnej kopii listy / zestawu lub zwracają daną kolekcję, jeśli jest już niemodyfikowalna, zobacz JDK-8191517
Marcono1234
6

Uważam, że chodzi o to, że nawet jeśli kolekcja jest niemodyfikowalna, nie gwarantuje to, że nie może się zmienić. Weźmy na przykład kolekcję, która eksmituje elementy, jeśli są zbyt stare. Niemodyfikowalność oznacza po prostu, że obiekt przechowujący odniesienie nie może go zmienić, a nie że nie może się zmienić. Prawdziwym tego przykładem jest Collections.unmodifiableListmetoda. Zwraca niemodyfikowalny widok listy. Odwołanie List, które zostało przekazane do tej metody, jest nadal modyfikowalne, dlatego lista może być modyfikowana przez dowolnego posiadacza przekazanego odwołania. Może to spowodować wystąpienie wyjątków ConcurrentModificationExceptions i innych złych rzeczy.

Niezmienne, oznaczają, że w żaden sposób nie można zmienić kolekcji.

Drugie pytanie: Niezmienna kolekcja nie oznacza, że ​​obiekty zawarte w kolekcji nie ulegną zmianie, po prostu ta kolekcja nie zmieni się w liczbie i składzie obiektów, które posiada. Innymi słowy, lista odniesień do kolekcji nie ulegnie zmianie. Nie oznacza to, że elementy wewnętrzne obiektu, do którego się odwołujemy, nie mogą się zmienić.

John B.
źródło
Dobry!! Więc jeśli utworzę opakowanie na Unmodifiable Collection i ustawię odwołanie do modyfikowalnej kolekcji jako prywatne, mogę być pewien, że jest niezmienne. dobrze?
AKS
Jeśli rozumiem twoje pytanie, odpowiedź brzmi: nie. Zawijanie Unmodifiable nie chroni go przed przekazaniem kolekcji do unmodifiableListzmodyfikowanej. Jeśli chcesz to zrobić, użyj ImmutableList.
John B
0

Pure4J wspiera to, czego szukasz, na dwa sposoby.

Po pierwsze, zapewnia @ImmutableValueadnotację, dzięki czemu można dodać adnotację do klasy, aby powiedzieć, że jest niezmienna. Istnieje wtyczka maven, która umożliwia sprawdzenie, czy kod faktycznie jest niezmienny (użycie finalitp.).

Po drugie, zapewnia trwałe kolekcje z Clojure (z dodanymi rodzajami) i zapewnia, że ​​elementy dodane do kolekcji są niezmienne. Wydajność tych jest najwyraźniej całkiem niezła. Kolekcje są niezmienne, ale zaimplementuj interfejsy kolekcji Java (i typy ogólne) do wglądu. Mutacja zwraca nowe kolekcje.

Zastrzeżenie: jestem twórcą tego

Rob Moffat
źródło