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

我很难理解Kotlin实际上在做什么:

我的unit testing看起来像这样:

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

我的解析器看起来像这样

 abstract class EnvelopeParser(val xml: String) { abstract fun parseResponse(response: Element) init { parseResponse(xmlFromString(xml)) } // non-related stuff } 

 class ReadCursorRequestParser(xml: String) : EnvelopeParser(xml) { var contacts: List = mutableListOf() override fun parseResponse(response: Element) { // here some parsing stuff, fills the contacts-list println("size is: ${contacts.size}") } } 

println说size is: 134 ,unit testing说: java.lang.AssertionError: Expected , actual

为什么?

正如你在评论中所说的, parseResponse(...)是从EnvelopeParser构造函数中调用的。

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

  1. 一个对象被分配。

  2. ReadCursorRequestParser构造函数被调用,它立即调用超类的构造函数。

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

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

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

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

这个简单的例子显示了这个行为:( 链接) 。


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

 lateinit var contacts: List 

有了这个声明,构造函数将不会重新分配contacts

但我宁愿强烈建议你避免在构造函数中调用open函数,因为如果被覆盖的话,他们可能(而且通常)依赖于尚未初始化派生类的状态,而且 他们所做改变将是被派生类构造函数覆盖 。 你甚至可能最终导致一部分变化持续下去,因为它们是超级状态,另一部分已经消失 – 绝对不是你想在日常生活中看到的东西。