我希望有一个数据类接受只读列表:
data class Notebook(val notes: List<String>) {
}
但它也可以接受MutableList
,因为它是List
的子类型。
例如,以下代码修改传入的列表:
fun main(args: Array<String>) {
val notes = arrayListOf("One", "Two")
val notebook = Notebook(notes)
notes.add("Three")
println(notebook) // prints: Notebook(notes=[One, Two, Three])
}
有没有办法在数据类中执行传入列表的防御性副本?
答案 0 :(得分:4)
您可以使用.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()
}
Kotlin团队正在研究真正不可变的集合,如果它们足够稳定可供使用,您可以预览它们:https://github.com/Kotlin/kotlinx.collections.immutable
另一种方法是创建一个允许使用后代类型MutableList
的接口。这正是Klutter library通过创建轻量级委托类的层次结构所做的事情,这些委托类可以包装列表以确保不可能发生突变。由于他们使用委托,他们几乎没有开销。您可以使用此库,或者仅查看源代码作为如何创建此类受保护集合的示例。然后,您更改您的方法以请求此受保护的版本而不是原始版本。有关提示,请参阅Klutter ReadOnly Collection Wrappers和associated tests的源代码。
作为使用Klutter中的这些类的示例,数据类将是:
data class Notebook(val notes: ReadOnlyList<String>) {
调用者将被迫通过传递一个非常简单的包装列表来遵守:
val myList = mutableListOf("day", "night")
Notebook(myList.toImmutable()) // copy and protect
正在发生的事情是调用者(通过调用asReadOnly()
)制作防御性副本以满足您的方法的要求,并且由于这些类的设计方式,无法改变受保护的副本。
Klutter实现中的一个缺陷是它没有ReadOnly
与Immutable
的单独层次结构,因此如果调用者调用asReadOnly()
,则列表的持有者仍然可能导致突变。因此,在您的此代码版本(或Klutter的更新)中,最好确保所有工厂方法始终复制,并且绝不允许以任何其他方式构造这些类(即构造构造函数{{1} })。或者有明确制作副本时使用的第二个层次结构。最简单的方法是将代码复制到您自己的库中,删除仅留下internal
的{{1}}方法,并使集合类构造函数全部为asReadOnly()
。
<小时/>
答案 1 :(得分:2)
我认为更好的方法是将JetBrains库用于不可变集合 - https://github.com/Kotlin/kotlinx.collections.immutable
在项目中导入
添加bintray存储库:
repositories {
maven {
url "http://dl.bintray.com/kotlin/kotlinx"
}
}
添加依赖项:
compile 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.1'
结果:
data class Notebook(val notes: ImmutableList<String>) {}
fun main(args: Array<String>) {
val notes = immutableListOf("One", "Two")
val notebook = Notebook(notes)
notes.add("Three") // creates a new collection
println(notebook) // prints: Notebook(notes=[One, Two])
}
注意: 您还可以使用使用ImmutableList的添加和删除方法,但此方法不会修改当前列表,只需为您创建新的更改并将其返回给您。
答案 2 :(得分:1)
无法覆盖主构造函数中声明的属性的赋值。如果您需要自定义作业,则必须将其移出构造函数,但是您不能再将您的类作为数据类。
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
如果你必须保留一个数据类,我看到这样做的唯一方法是使用init
块来制作副本,但是你必须使你的属性成为{{1能够做到这一点,因为属性的第一次分配将自动发生,并且在此之后您将对其进行修改。
var
答案 3 :(得分:1)
您应该将JetBrains库用于不可变的集合, import this library in your app project。
添加Bintray存储库:
repositories {
maven {
url "http://dl.bintray.com/kotlin/kotlinx"
}
}
然后添加依赖项:
implementation 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.1
结果将是
data class Notebook(val notes: ImmutableList<String>) {}
fun main(args: Array<String>) {
val notes = immutableListOf("One", "Two")
val notebook = Notebook(notes)
notes.add("Three") // creates a new collection
println(notebook) // prints: Notebook(notes=[One, Two])
}