Kotlin:超级构造函数中所做的更改被覆盖
我很难理解Kotlin实际上在做什么:
我的单元测试看起来像这样:
@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<AddressBookElementParser> = mutableListOf() override fun parseResponse(response: Element) { // here some parsing stuff, fills the contacts-list println("size is: ${contacts.size}") } }
println说size is: 134
,单元测试说: java.lang.AssertionError: Expected <134>, actual <0>
。
为什么?
正如你在评论中所说的, parseResponse(...)
是从EnvelopeParser
构造函数中调用的。
那么当你创建一个ReadCursorRequestParser
的实例时会发生什么:
-
一个对象被分配。
-
ReadCursorRequestParser
构造函数被调用,它立即调用超类的构造函数。 -
超级构造函数(
EnvelopeParser
)调用parseResponse(...)
,从而分配contacts
(此时这实际上是一个非空列表)。 -
超级构造函数然后返回,
ReadCursorRequestParser
的构造ReadCursorRequestParser
继续。 -
ReadCursorRequestParser
构造函数再次分配contacts
,现在它是一个空列表 。
原因是每个构造函数首先调用它的超级构造函数(如果有的话),然后才初始化属性并执行init
块,超级构造者对类中所声明的状态所做的所有更改(而不是基类)将会被类自己的构造函数覆盖。
这个简单的例子显示了这个行为:( 链接) 。
最简单的解决方法是将contacts
的声明更改为类似的
lateinit var contacts: List<AddressBookElementParser>
有了这个声明,构造函数将不会重新分配contacts
。
但我宁愿强烈建议你避免在构造函数中调用open
函数,因为如果被覆盖的话,他们可能(而且通常会)依赖于尚未初始化的派生类的状态,而且 他们所做的改变将会是被派生类构造函数覆盖 。 你甚至可能最终导致部分变化持续下去,因为它们已经超越了超类,而另一部分已经消失 – 绝对不是你想在日常生活中看到的东西。