我无法理解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>
。
为什么?
答案 0 :(得分:5)
正如您在评论中所说,parseResponse(...)
是从EnvelopeParser
构造函数内部调用的。
然后,当您创建ReadCursorRequestParser
的实例时,发生了什么:
分配了一个对象。
调用ReadCursorRequestParser
构造函数,它会立即调用超类构造函数。
超级构造函数(EnvelopeParser
的)会调用parseResponse(...)
,从而分配contacts
(此时这实际上是一个非空列表)。
然后超级构造函数返回,ReadCursorRequestParser
的构造函数继续。
ReadCursorRequestParser
构造函数再次指定contacts
,现在它是一个空列表。
原因是每个构造函数首先调用其超级构造函数(如果有的话),然后才初始化属性并执行init
块,以及超级构造函数对类中声明的状态所做的所有更改(不是基类)将被班级覆盖。自己的构造函数。
此简化示例显示了此行为:(link)。
最简单的解决方法是将contacts
的声明更改为
lateinit var contacts: List<AddressBookElementParser>
使用此声明,构造函数不会重新分配contacts
。
但是我强烈建议你不要在构造函数中调用open
函数,因为如果被覆盖,它们可能(并且通常会)依赖于派生类状态,而不是已初始化,派生类构造函数也会覆盖所做的更改。你甚至可以最终保留一些部分变更,因为它们是在超类状态下完成而另一部分被删除 - 绝对不是你想在日常生活中看到的。