kotlin custom从mutableList获取不可变列表

时间:2018-06-01 17:53:29

标签: 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方法。 有没有办法调用mutabbleList本身而不是getter方法?

4 个答案:

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

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