我有一个可变列表的自定义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方法。 有没有办法调用mutabbleList本身而不是getter方法?
答案 0 :(得分:2)
当然它会调用getter(返回ImmutableList.copyOf(field)
)。
您可以在mutableList
区块中轻松分配init
新复制的可变列表:
data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo>
get() = ImmutableList.copyOf(field)
init {
mutableList = list.map { it.copy() }.toMutableList()
}
}
或whithout init
:
data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo> = list.map { it.copy() }.toMutableList()
get() = ImmutableList.copyOf(field)
}
答案 1 :(得分:0)
Kotlin stdlib选择接口不变性。这就是说,实现所使用的接口决定了引用本身的可变性。
因此,将MutableList<T>
变成List<T>
的正确方法是将其装箱,如下所示:
val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList as List<Int>
这样,作为myImmutableList
中的List<Int>
引用,它将仅公开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
关键字,该关键字允许您通过现有的实现来实现接口。也就是说,您将传递给MutableList<T>
的构造函数的ImmutableList<T>
。
答案 2 :(得分:0)
当我研究这个主题时,对我来说最好的解决方案就是按合同强制执行。如果要创建可变列表,请说:
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
)
现在,如果我们与其他组件共享该列表,我们将使用正确的抽象,而不会产生任何费用。我们还可以使用Collection,因为List继承自Collection:
val immutableList: Collection<Randomy> = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
)
答案 3 :(得分:0)
对我来说,使用 var 代替 val 字段和私有 setter 通常效果最好
class Order
class Something() {
var orders: List<Order> = listOf()
private set
fun addOrder(order: Order) {
orders = orders
.toMutableList()
.apply { add(order) }
}
}
这将其公开为不可变的,并且只需要一个字段。我们付出的代价是在添加元素时创建一个新集合的开销。