Background:
In Java, immutability of collections is achieved through wrappers like Collections.unmodifiableList() and designing immutable classes manually (final fields, absence of setters). In Android and server-side development, this became cumbersome and inconvenient, while thread safety and data predictability were compromised.
Problem:
Java collections are not safe by default; you can always obtain a reference and modify the object. This complicates the control over mutability, especially in multithreaded environments, and leads to hard-to-trace bugs.
Solution:
Kotlin was initially designed with a separation of read-only and mutable collections, and creating truly immutable objects became easier with val, data class, and List/Set/Map without the mutable prefix. Moreover, copying through copy() methods was introduced.
Code example:
val nums: List<Int> = listOf(1, 2, 3) // immutable list // nums.add(4) // compilation error val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // Example with immutable map val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)
Key features:
Does val guarantee absolute immutability of the nested object?
No! val only guarantees that the variable cannot point to another object, but the fields of the object can be changed (if the internal properties are var).
Can a mutable object be obtained from a read-only collection?
Yes — if the collection contains a mutable object, its state can be changed despite the read-only interface of the collection.
data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // allowed
What happens when casting List<Int> to MutableList<Int> using as?
It will compile, but it will lead to ClassCastException at runtime if the original type was a read-only interface.
An Android application stores state in val List<User], considering it immutable. One of the developers modifies user.name. In another fragment, the updated list unexpectedly displays inconsistent data — the bug appears in the release.
Pros:
Cons:
Using immutable data class only with val fields, filtering access to nested objects, updating state through copy().
Pros:
Cons: