6

我想要一个接受只读列表的数据类:

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])
}

有没有办法在数据类中执行传入列表的防御性副本?

4

4 回答 4

5

选项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 实现中的一个缺陷是它没有单独的ReadOnlyvs层次结构,Immutable因此如果调用者改为调用asReadOnly()列表的持有者,仍然会导致突变。因此,在您的此代码版本(或对 Klutter 的更新)中,最好确保您的所有工厂方法始终制作副本,并且绝不允许以任何其他方式构造这些类(即制作构造函数internal)。或者有第二个层次结构,在清楚地制作副本时使用。最简单的方法是将代码复制到自己的库中,删除asReadOnly()方法只留下toImmutable(),并将集合类构造函数全部设为internal.


更多信息

另请参阅: Kotlin 和不可变集合?

于 2017-06-08T12:23:00.527 回答
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 使用 add 和 remove 方法,但此方法不会修改当前列表,只会使用您的更改创建新列表并将其返回给您。

于 2017-06-08T12:45:19.203 回答
1

无法覆盖主构造函数中声明的属性的分配。如果您需要自定义分配,则必须将其移出构造函数,但是您不能再使您的类成为数据类。

class Notebook(notes: List<String>) {
    val notes: List<String> = notes.toList()
}

如果您必须将其保留为数据类,我认为这样做的唯一方法是使用该init块来制作副本,但您必须使您的属性 avar能够做到这一点,因为第一次分配属性将自动发生,然后您将在此处对其进行修改。

data class Notebook(var notes: List<String>) {
    init {
        notes = notes.toList()
    }
}
于 2017-06-08T12:19:42.380 回答
1

您应该将 JetBrains 库用于不可变集合, 将此库导入您的应用项目中。

添加 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])
}
于 2018-10-29T09:46:52.170 回答