选项1
您可以使用.toList()
来制作副本,尽管您需要在内部使用另一个属性来保存列表副本,或者您需要从数据类移到普通类。
作为数据类:
data class Notebook(private val _notes: List<String>) {
val notes: List<String> = _notes.toList()
}
这里的问题是您的数据类将具有.equals()
并.hashCode()
基于一个潜在的变异列表。
所以替代方法是使用普通类:
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
选项 2
Kotlin 团队也在研究真正不可变的集合,如果它们足够稳定以供使用,您也许可以预览它们: https ://github.com/Kotlin/kotlinx.collections.immutable
选项 3
另一种方法是创建一个允许使用后代类型的接口MutableList
。这正是Klutter 库通过创建轻量级委托类的层次结构所做的,这些委托类可以包装列表以确保不会发生突变。由于他们使用委托,因此他们的开销很小。您可以使用这个库,或者只是查看源代码作为如何创建这种类型的受保护集合的示例。然后你改变你的方法来请求这个受保护的版本而不是原始版本。请参阅Klutter ReadOnly Collection Wrappers和相关测试的源代码以获取想法。
作为使用来自Klutter的这些类的示例,数据类将是:
data class Notebook(val notes: ReadOnlyList<String>) {
调用者将被迫通过传入一个非常简单的包装列表来遵守:
val myList = mutableListOf("day", "night")
Notebook(myList.toImmutable()) // copy and protect
正在发生的事情是调用者(通过调用asReadOnly()
)正在制作防御性副本以满足您的方法的要求,并且由于这些类的设计方式,没有办法改变受保护的副本。
Klutter 实现中的一个缺陷是它没有单独的ReadOnly
vs层次结构,Immutable
因此如果调用者改为调用asReadOnly()
列表的持有者,仍然会导致突变。因此,在您的此代码版本(或对 Klutter 的更新)中,最好确保您的所有工厂方法始终制作副本,并且绝不允许以任何其他方式构造这些类(即制作构造函数internal
)。或者有第二个层次结构,在清楚地制作副本时使用。最简单的方法是将代码复制到自己的库中,删除asReadOnly()
方法只留下toImmutable()
,并将集合类构造函数全部设为internal
.
更多信息
另请参阅: Kotlin 和不可变集合?