Kotlin 自定义从可变列表中获取不可变列表



我有一个自定义的 getter 方法,用于可变列表,以使用 Google 的 Guava 库返回一个不可处理的列表。然后在构造函数中访问此可变列表。

data class mutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo>
get() = ImmutableList.copyOf(field)
init {
mutableList = mutableListOf()
list.forEach {
mutableList.add(it.copy()) // Exception is thrown here.
// It actually calls its getter method which is an immutable 
// list, so when init this class, it throw exception                   
}
}
}
data class Foo {}

我把它反编译成Java,在init块中,它调用了mutableList的getter方法。 有没有办法调用 muttabbleList 本身而不是 getter 方法?

当然,它调用getter(返回ImmutableList.copyOf(field)(。

您可以简单地在init块中mutableList新复制的可变列表进行分配:

data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo>
get() = ImmutableList.copyOf(field)
init {
mutableList = list.map { it.copy() }.toMutableList()
}
}

init

data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo> = list.map { it.copy() }.toMutableList()
get() = ImmutableList.copyOf(field)
}

Kotlin stdlib 选择了接口不变性。这意味着,实现装箱的接口决定了引用本身的可变性。

因此,将MutableList<T>List<T>的正确方法是将其装箱,如下所示:

val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList as List<Int>

这样,作为List<Int>框中的myImmutableList引用,它只会从List<Int>中暴露成员,而不是那些MutableList<Int>定义的,这允许改变对象的状态,因此列表。

然后,如果你真的想避免以下问题(从上面的代码恢复(,

val hackedList = myImmutableList as MutableList<Int>

。对于您可以通过拆箱访问可变实现,您可能选择以下解决方案:

class ImmutableList<T>(list: MutableList<T>) : List<T> by list
fun <T> MutableList<T>.toImmutable() = ImmutableList(this)

然后按如下方式使用它:

val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList.toImmutable()

因此,您将避免上述问题。事实上,任何试图从MutableList<T>.toImmutable()中解开值返回的尝试最终都会TypeCastException,因为List<T>的实现不再是MutableList<T>。相反,它是一个ImmutableList<T>,它不会公开任何可能改变对象的方法。

与@Lucas方法不同,这样您就不会浪费时间来复制元素,因为您将依赖 Kotlin 中的by关键字,它允许您通过现有的实现实现接口。也就是说,您将传递给ImmutableList<T>的构造函数的MutableList<T>

当我研究这个主题时,它对我有用的最佳解决方案就是通过合同执行。如果要创建可变列表,假设:

val immutableList  = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
).toList() // We make it immutable?

然后您使用扩展函数或下面给出的任何建议,例如使用ImmutableList.copyOf(field),您可能会支付罚款,因为您要将项目复制到另一个集合中。

另一种选择是支付诸如此类操作的拆箱成本:

val myImmutableList = myMutableList as List<Int>

我选择的解决方案只是通过合同执行,这是一个非常简单的概念。您的MutableList继承自List.如果要共享具有该抽象级别的项集合,可以选择通过强制实施以下类型来实现:

val immutableList: List<Randomy> = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
)

现在,如果我们与另一个组件共享该列表,我们将使用正确的抽象而无需任何成本。我们也可以使用集合,因为 List 继承自集合:

val immutableList: Collection<Randomy> = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
)

对我来说,使用 var 而不是 val 字段以及私人二传手通常效果最好

class Order
class Something() {
var orders: List<Order> = listOf()
private set
fun addOrder(order: Order) {
orders = orders
.toMutableList()
.apply { add(order) }
}
}

这会将其公开为不可变,并且只需要单个字段。我们付出的代价是在添加新集合时创建新集合的开销。

最新更新