我无法理解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= 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
构造函数分配contacts
一次,现在是一个空列表.
原因是每个构造函数首先调用它的超级构造函数(如果有的话),然后才初始化属性并执行init
块,超级构造器对类(而不是基类)中声明的状态所做的所有更改都将是由类自己的构造函数覆盖.
此简化示例显示了此行为:( 链接).
最简单的解决方法是将声明更改为contacts
类似的
lateinit var contacts: List
使用此声明,构造函数将不会重新分配contacts
.
但我强烈建议你避免open
在构造函数中调用函数,因为如果被覆盖,它们可能(通常会)依赖于尚未初始化的派生类状态,并且它们所做的更改也会被覆盖由派生类构造函数.你甚至可以最终保留一些部分变化,因为它们是在超类状态下完成而另一部分被删除 - 绝对不是你想在日常生活中看到的.