Kotlin 从内部类的构造函数访问外部类抛出 NPE
这段代码按预期工作: https://pl.kotl.in/xaSu-xe9u
interface M {
fun hello()
}
abstract class Parent : M {
init {
this.hello()
}
}
class Outer : M {
override fun hello() {
println("hello, world")
}
inner class Inner : Parent(), M by this {
override fun hello() {
[email protected]()
}
}
}
fun main() {
val out = Outer()
val inner = out.Inner()
}
但是下面的代码会抛出 NPE: https://pl.kotl.in/PfogCZBSW
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Outer.hello()" because "this.$$delegate_0" is null
at Outer$Inner.hello(Bug.kt)
at Parent.<init>(Bug.kt:9)
at Outer$Inner.<init>(Bug.kt:18)
at BugKt.main(Bug.kt:23)
at BugKt.main(Bug.kt)
interface M {
fun hello()
}
abstract class Parent : M {
init {
this.hello()
}
}
class Outer : M {
override fun hello() {
println("hello, world")
}
inner class Inner : Parent(), M by this
}
fun main() {
val out = Outer()
val inner = out.Inner()
}
为什么?这是编译器错误吗?
我们可能会争论这是否是一个编译器错误,但我认为像这样搞乱对象初始化是自找麻烦。对我来说,在构造函数中调用抽象函数本身就是一个危险信号。您使用多种高级功能(例如委派),并希望一切都能按照您需要的顺序进行。它可能有效也可能无效。
我们可以在生成的字节码中找到准确的解释:
L0
LINENUMBER 22 L0
ALOAD 0
ALOAD 1
PUTFIELD Outer$Inner.this$0 : LOuter;
ALOAD 0
L1
LINENUMBER 22 L1
INVOKESPECIAL Parent.<init> ()V
ALOAD 0
ALOAD 1
PUTFIELD Outer$Inner.$$delegate_0 : LOuter;
RETURN
首先,它设置
Outer
,然后调用超级构造函数,然后才设置 Outer
委托。如果我们意识到父构造函数通常在设置子类声明的字段之前执行,这是有道理的。它也符合我们在源代码中看到的顺序:Parent(), M by this
。