Kotlin:超级构造函数中所做的更改将被覆盖

Joh*_*ith -1 kotlin

我无法理解Kotlin实际上在做什么:

我的单元测试看起来像这样:

@Test
fun testReadCursorRequest() {
    val xml = fromFile()
    val parser: ReadCursorRequestParser = ReadCursorRequestParser(xml)
    assertEquals(0, parser.status)
    assertEquals(134, parser.contacts!!.size)
}
Run Code Online (Sandbox Code Playgroud)

我的解析器看起来像这样

abstract class EnvelopeParser(val xml: String) {
    abstract fun parseResponse(response: Element)

    init {
        parseResponse(xmlFromString(xml))
    }

    // non-related stuff
}
Run Code Online (Sandbox Code Playgroud)

class ReadCursorRequestParser(xml: String) : EnvelopeParser(xml) {

    var contacts: List<AddressBookElementParser> = mutableListOf()     

    override fun parseResponse(response: Element) {
        // here some parsing stuff, fills the contacts-list
        println("size is: ${contacts.size}")
    }
}
Run Code Online (Sandbox Code Playgroud)

println说size is: 134,单元测试说:java.lang.AssertionError: Expected <134>, actual <0>.

为什么?

hot*_*key 5

正如您在评论parseResponse(...)中所说,从EnvelopeParser构造函数内部调用.

那么当你创建一个实例时发生了什么ReadCursorRequestParser:

  1. 分配对象.

  2. ReadCursorRequestParser构造函数被调用,并调用父类的构造函数立竿见影.

  3. 超级构造函数(那个EnvelopeParser)调用parseResponse(...)并因此分配contacts(此时这实际上是一个非空列表).

  4. 然后超级构造函数返回,构造函数ReadCursorRequestParser继续.

  5. ReadCursorRequestParser构造函数分配contacts一次,现在是一个空列表.

原因是每个构造函数首先调用它的超级构造函数(如果有的话),然后才初始化属性并执行init块,超级构造器对类(而不是基类)中声明的状态所做的所有更改都将是由类自己的构造函数覆盖.

此简化示例显示了此行为:( 链接).


最简单的解决方法是将声明更改为contacts类似的

lateinit var contacts: List<AddressBookElementParser>
Run Code Online (Sandbox Code Playgroud)

使用此声明,构造函数将不会重新分配contacts.

但我强烈建议你避免open在构造函数中调用函数,因为如果被覆盖,它们可能(通常会)依赖于尚未初始化派生类状态,并且它们所做更改也会被覆盖由派生类构造函数.你甚至可以最终保留一些部分变化,因为它们是在超类状态下完成而另一部分被删除 - 绝对不是你想在日常生活中看到的.