Kotlin 数据中可变集合的防御副本 class
Defensive copy of a mutable collection in Kotlin data class
我想要一个接受只读列表的数据class:
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])
}
有没有办法执行数据中传入列表的防御副本class?
无法覆盖在主构造函数中声明的 属性 的赋值。如果您需要自定义赋值,则必须将其移出构造函数,但您无法再将 class 设为数据 class.
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
如果您必须将其保存为数据 class,我认为这样做的唯一方法是使用 init
块来制作副本,但您必须制作您的 属性 a var
才能做到这一点,因为 属性 的第一个赋值会自动发生,之后您将在这里修改它。
data class Notebook(var notes: List<String>) {
init {
notes = notes.toList()
}
}
选项 1
您可以使用 .toList()
来制作副本,尽管您需要另一个 属性 内部保存列表副本,或者您需要从数据 class 移动到正常 class.
作为数据class:
data class Notebook(private val _notes: List<String>) {
val notes: List<String> = _notes.toList()
}
这里的问题是您的数据 class 将根据潜在的变异列表具有 .equals()
和 .hashCode()
。
所以替代方法是使用正常的 class:
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
选项 2
Kotlin 团队也在研究真正不可变的集合,如果它们足够稳定可供使用,您也许可以预览它们:https://github.com/Kotlin/kotlinx.collections.immutable
选项 3
另一种方法是创建一个允许使用 MutableList
的后代类型的接口。这正是 Klutter library does by creating a hierarchy of light-weight delegating classes that can wrap lists to ensure no mutation is possible. Since they use delegation they have little overhead. You can use this library, or just look at the source code as an example of how to create this type of protected collections. Then you change your method to ask for this protected version instead of the original. See the source code for Klutter ReadOnly Collection Wrappers and associated tests 的想法。
作为使用来自 Klutter 的这些 class 的示例,数据 class 将是:
data class Notebook(val notes: ReadOnlyList<String>) {
并且调用者将通过传递一个非常简单的包装列表来强制遵守:
val myList = mutableListOf("day", "night")
Notebook(myList.toImmutable()) // copy and protect
发生的事情是调用者(通过调用 asReadOnly()
)正在制作防御副本以满足您的方法的要求,并且由于这些 classes 是设计出来的。
Klutter 实现中的一个缺陷是它没有针对 ReadOnly
与 Immutable
的单独层次结构,因此如果调用者改为调用 asReadOnly()
,则列表的持有者可以还是会引起突变。因此,在您的此代码版本(或 Klutter 的更新版)中,最好确保您的所有工厂方法始终制作副本,并且绝不允许以任何其他方式构造这些 classes(即 make构造函数 internal
)。或者有一个第二层级,当副本已经明确制作时使用。最简单的方法是将代码复制到您自己的库中,删除 asReadOnly()
方法,只留下 toImmutable()
并使集合 class 构造函数全部为 internal
.
更多信息
另请参阅:
我认为更好的方法是将 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 使用添加和删除方法,但此方法不会修改当前列表,只会根据您的更改创建一个新列表,并 return 为您创建。
您应该将 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])
}
我想要一个接受只读列表的数据class:
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])
}
有没有办法执行数据中传入列表的防御副本class?
无法覆盖在主构造函数中声明的 属性 的赋值。如果您需要自定义赋值,则必须将其移出构造函数,但您无法再将 class 设为数据 class.
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
如果您必须将其保存为数据 class,我认为这样做的唯一方法是使用 init
块来制作副本,但您必须制作您的 属性 a var
才能做到这一点,因为 属性 的第一个赋值会自动发生,之后您将在这里修改它。
data class Notebook(var notes: List<String>) {
init {
notes = notes.toList()
}
}
选项 1
您可以使用 .toList()
来制作副本,尽管您需要另一个 属性 内部保存列表副本,或者您需要从数据 class 移动到正常 class.
作为数据class:
data class Notebook(private val _notes: List<String>) {
val notes: List<String> = _notes.toList()
}
这里的问题是您的数据 class 将根据潜在的变异列表具有 .equals()
和 .hashCode()
。
所以替代方法是使用正常的 class:
class Notebook(notes: List<String>) {
val notes: List<String> = notes.toList()
}
选项 2
Kotlin 团队也在研究真正不可变的集合,如果它们足够稳定可供使用,您也许可以预览它们:https://github.com/Kotlin/kotlinx.collections.immutable
选项 3
另一种方法是创建一个允许使用 MutableList
的后代类型的接口。这正是 Klutter library does by creating a hierarchy of light-weight delegating classes that can wrap lists to ensure no mutation is possible. Since they use delegation they have little overhead. You can use this library, or just look at the source code as an example of how to create this type of protected collections. Then you change your method to ask for this protected version instead of the original. See the source code for Klutter ReadOnly Collection Wrappers and associated tests 的想法。
作为使用来自 Klutter 的这些 class 的示例,数据 class 将是:
data class Notebook(val notes: ReadOnlyList<String>) {
并且调用者将通过传递一个非常简单的包装列表来强制遵守:
val myList = mutableListOf("day", "night")
Notebook(myList.toImmutable()) // copy and protect
发生的事情是调用者(通过调用 asReadOnly()
)正在制作防御副本以满足您的方法的要求,并且由于这些 classes 是设计出来的。
Klutter 实现中的一个缺陷是它没有针对 ReadOnly
与 Immutable
的单独层次结构,因此如果调用者改为调用 asReadOnly()
,则列表的持有者可以还是会引起突变。因此,在您的此代码版本(或 Klutter 的更新版)中,最好确保您的所有工厂方法始终制作副本,并且绝不允许以任何其他方式构造这些 classes(即 make构造函数 internal
)。或者有一个第二层级,当副本已经明确制作时使用。最简单的方法是将代码复制到您自己的库中,删除 asReadOnly()
方法,只留下 toImmutable()
并使集合 class 构造函数全部为 internal
.
更多信息
另请参阅:
我认为更好的方法是将 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 使用添加和删除方法,但此方法不会修改当前列表,只会根据您的更改创建一个新列表,并 return 为您创建。
您应该将 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])
}