Kotlin内部类构造器访问外部类抛出NPE的原因探究
Kotlin代码空指针异常原因分析
两段代码对比
运行正常的代码
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() { this@Outer.hello() } } } fun main() { val out = Outer() val inner = out.Inner() }
抛出空指针异常的代码
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() }
异常信息
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)
问题原因
核心问题在于类初始化顺序和接口委托的变量初始化时机:
- 创建
Inner实例时,会先执行父类Parent的初始化块,此时会调用hello()方法 M by this的接口委托逻辑,会被编译器转化为子类Inner的一个成员变量$$delegate_0,用来存储委托对象(即外部类Outer的实例),而这个变量的初始化是在Inner自身构造代码执行阶段,晚于父类的初始化流程- 第二段代码中
Inner没有重写hello(),父类初始化时调用的hello()会尝试委托给未初始化的$$delegate_0,直接触发空指针异常 - 第一段代码中
Inner手动重写了hello(),直接调用外部类的hello()方法,跳过了委托逻辑,不需要依赖未初始化的委托变量,因此可以正常运行
是否属于编译器Bug?
这不属于编译器Bug,完全符合Kotlin的初始化规则:
- 父类初始化总是优先于子类成员(包括委托生成的成员)的初始化
- 接口委托生成的实例变量本质是子类成员,遵循子类成员初始化晚于父类的规则
- 这种情况属于初始化阶段调用可重写方法的典型陷阱,Kotlin官方文档也明确提醒过:在父类初始化时调用子类的可重写方法存在风险,可能访问到未初始化的状态
内容的提问来源于stack exchange,提问作者Tan Telephone
相关产品推荐
相关产品推荐

