问题的历史:
在 Java 中,集合的不可变性是通过 Collections.unmodifiableList() 等包装器以及手动设计 immutable 类(final 字段,无 setter)。在 Android 和服务器开发中,这种做法变得麻烦且不方便,线程安全和数据的可预测性也受到影响。
问题:
Java 集合默认情况下不安全,可以随时获取引用并修改对象。这使得控制可变性变得复杂,尤其是在多线程环境下,并导致难以发现的bug。
解决方案:
Kotlin 从一开始就设计了只读(read-only)和可变(mutable)集合的分离,通过使用 val、data class 以及没有 mutable 前缀的 List/Set/Map,使得创建真正的不可变(truly immutable)对象变得更加简单。在此基础上,通过 copy() 方法进行复制。
代码示例:
val nums: List<Int> = listOf(1, 2, 3) // 不可变列表 // nums.add(4) // 编译错误 val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // 不可变 map 的示例 val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)
关键特点:
val 是否保证嵌套对象的绝对不可变性?
不是!val 只保证变量不能指向另一个对象,但对象的字段可以被修改(如果内部属性是 var)。
可以从只读集合中获得可变对象吗?
可以——如果集合包含可变对象,可以改变其状态,尽管集合的接口是只读的。
data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // 允许
通过 as? 将 List<Int> 转换为 MutableList<Int> 会发生什么?
它会编译,但如果原始类型是只读接口,将在运行时导致 ClassCastException。
一个 Android 应用程序在 val List<User> 中存储状态,认为它是不可变的。开发者之一修改了 user.name。在另一个片段中,更新后的列表意外地显示不一致的数据——在发布时出现了 bug。
优点:
缺点:
仅使用 val 字段的不可变数据类,过滤对嵌套对象的访问,通过 copy() 更新状态。
优点:
缺点: