Kotlin数据类中可变集合的防御性副本

时间:2017-06-08 12:09:18

标签: kotlin data-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])
}

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

4 个答案:

答案 0 :(得分:4)

选项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 library通过创建轻量级委托类的层次结构所做的事情,这些委托类可以包装列表以确保不可能发生突变。由于他们使用委托,他们几乎没有开销。您可以使用此库,或者仅查看源代码作为如何创建此类受保护集合的示例。然后,您更改您的方法以请求此受保护的版本而不是原始版本。有关提示,请参阅Klutter ReadOnly Collection Wrappersassociated tests的源代码。

作为使用Klutter中的这些类的示例,数据类将是:

data class Notebook(val notes: ReadOnlyList<String>) {

调用者将被迫通过传递一个非常简单的包装列表来遵守:

val myList = mutableListOf("day", "night")
Notebook(myList.toImmutable())  // copy and protect

正在发生的事情是调用者(通过调用asReadOnly())制作防御性副本以满足您的方法的要求,并且由于这些类的设计方式,无法改变受保护的副本。

Klutter实现中的一个缺陷是它没有ReadOnlyImmutable的单独层次结构,因此如果调用者调用asReadOnly(),则列表的持有者仍然可能导致突变。因此,在您的此代码版本(或Klutter的更新)中,最好确保所有工厂方法始终复制,并且绝不允许以任何其他方式构造这些类(即构造构造函数{{1} })。或者有明确制作副本时使用的第二个层次结构。最简单的方法是将代码复制到您自己的库中,删除仅留下internal的{​​{1}}方法,并使集合类构造函数全部为asReadOnly()

<小时/>

更多信息

另见: Kotlin and Immutable Collections?

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