Historia pytania:
W Javie niemutowalność kolekcji osiąga się za pomocą opakowań typu Collections.unmodifiableList() oraz projektowania klas immutable ręcznie (pola final, brak setterów). W praktyce programowania na Androida i w rozwoju serwerów stało się to kosztowne i niewygodne, a bezpieczeństwo wątków i przewidywalność danych zostały utracone.
Problem:
Kolekcje Javy są domyślnie niezabezpieczone, zawsze można uzyskać referencję i zmieniać obiekt. Utrudnia to kontrolę nad niemutowalnością, zwłaszcza w środowiskach wielowątkowych, i prowadzi do trudnych do wykrycia błędów.
Rozwiązanie:
Kotlin został zaprojektowany z podziałem na kolekcje read-only i mutable, a tworzenie naprawdę niemutowalnych obiektów stało się prostsze dzięki użyciu val, data class oraz List/Set/Map bez przedrostka mutable. Na tym tle - wykorzystanie kopiowania przez metody copy().
Przykład kodu:
val nums: List<Int> = listOf(1, 2, 3) // niemutowalna lista // nums.add(4) // błąd kompilacji val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // Przykład z niemutowalnym map val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)
Kluczowe cechy:
Czy val gwarantuje absolutną niemutowalność zagnieżdżonego obiektu?
Nie! val gwarantuje tylko to, że zmienna nie może wskazywać na inny obiekt, ale pola obiektu można zmieniać (jeśli wewnętrzne właściwości var).
Czy można uzyskać mutowalny obiekt z kolekcji read-only?
Tak — jeśli kolekcja zawiera zmienny obiekt, można zmieniać jego stan, mimo interfejsu read-only kolekcji.
data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // dozwolone
Co się stanie przy rzutowaniu List<Int> na MutableList<Int> poprzez as?
Skopiuje się, ale wywoła ClassCastException w czasie wykonywania, jeśli pierwotny typ był interfejsem read-only.
Aplikacja na Androida przechowuje stan w val List<User>, sądząc, że jest to niemutowalne. Jeden z programistów modyfikuje user.name. W innym fragmencie zaktualizowana lista niespodziewanie wyświetla niespójne dane — błąd pojawia się w wersji produkcyjnej.
Zalety:
Wady:
Użycie immutable data class wyłącznie z polami val, filtrowanie dostępu do zagnieżdżonych obiektów, aktualizacja stanu przez copy().
Zalety:
Wady: