初始化块如何处理重写的属性?

我试图了解为什么下面的代码抛出:

open class Base(open val input: String) { lateinit var derived: String init { derived = input.toUpperCase() // throws! } } class Sub(override val input: String) : Base(input) 

当像这样调用这个代码时:

 println(Sub("test").derived) 

它会抛出一个异常,因为在调用toUpperCase的时候, input解析为null 。 我发现这个计数器很直观:我将一个非空值传递给主构造函数,然后在超类的init块中解析为空?

我想我有一个模糊的想法可能会发生什么事情:因为input作为一个构造函数参数以及一个属性,分配内部调用this.input ,但是this还没有完全初始化。 这真的很奇怪:在IntelliJ调试器中, input正常解析(值为“test”),但只要我调用表达式求值窗口并手动检查input ,它就会突然为空。

假设这是预期的行为,你建议做什么,而不是一个需要初始化从同一类的属性派生的字段?

更新:我已经发布了两个更简洁的代码片断,说明混淆源于哪里:

https://gist.github.com/mttkay/9fbb0ddf72f471465afc https://gist.github.com/mttkay/5dc9bde1006b70e1e8ba

原始示例等同于以下Java程序:

 class Base { private String input; private String derived; Base(String input) { this.input = input; this.derived = getInput().toUpperCase(); // Initializes derived by calling an overridden method } public String getInput() { return input; } } class Derived extends Base { private String input; public Derived(String input) { super(input); // Calls the superclass constructor, which tries to initialize derived this.input = input; // Initializes the subclass field } @Override public String getInput() { return input; // Returns the value of the subclass field } } 

getInput()方法在Sub类中被覆盖,所以代码调用Sub.getInput()。 此时,Sub类的构造函数还没有执行,所以保存Sub.input值的后台依然为空。 这不是Kotlin中的一个错误; 您可以轻松地在纯Java代码中遇到相同的问题。

修复是不覆盖该属性。 (我已经看到了你的评论,但这并不能解释你为什么需要重写它。)

混淆来自于你为input值创建了两个存储(JVM中的字段)。 一个是基类,一个是派生的。 当你在基类中读取input值时,它会调用虚拟getInput方法。 getInput在派生类中被覆盖,以返回它自己的存储值,这个值在基类构造函数被调用之前没有被初始化。 这是典型的“构造函数中的虚拟调用”问题。

如果你改变派生类实际使用超级类型的属性,一切都很好。

 class Sub(input: String) : Base(input) { override val input : String get() = super.input }