You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 19:27:32