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

Kotlin继承父类时初始化块触发属性为空,抛出类型转换异常

解决Kotlin子类继承时父类初始化块触发属性为null的类型转换异常

这个坑我之前踩过!本质是Kotlin类初始化顺序和可重写属性绑定机制导致的,咱们一步步拆解:

问题根源

当你创建SubClass实例时,初始化顺序是这样的:

  1. 先执行父类Super的初始化逻辑,但注意——子类重写的var属性会在父类初始化阶段就被绑定到子类的实现上,此时子类的name还没完成初始化(子类的属性初始化要等父类完全初始化完才会执行)。
  2. 所以父类初始化块里访问的name其实是子类那个还没赋值的属性,值为null,调用toUpperCase()自然就抛出TypeCastException了。

你运行代码后看到的异常应该是这样:

INIT block fired with : null
Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String

几种可行的解决方案

方案1:用val替代var(如果属性不需要修改)

如果name不需要后续被重新赋值,换成val就能避开这个问题。因为val的重写不会在父类初始化阶段就触发子类的属性绑定,父类会使用自己的初始值:

open class Super {
    open val name : String = "Name1"
    init {
        println("INIT block fired with : $name")
        // val不能重新赋值,这里直接打印大写后的结果就行
        println(name.uppercase())
    }
}
class SubClass(newName : String) : Super() {
    override val name : String = "Mr. $newName"
}
fun main(args: Array<String>) {
    val obj = SubClass("John")
    println(obj.name)
}

运行结果:

INIT block fired with : Name1
NAME1
Mr. John

方案2:父类用私有属性+开放getter(适合必须用var的场景)

把父类的属性拆分成私有存储属性和开放的var访问器,这样父类初始化块操作的是自己的私有属性,不会被子类的重写影响:

open class Super {
    private var _name: String = "Name1"
    open var name: String
        get() = _name
        set(value) { _name = value }

    init {
        println("INIT block fired with : $_name")
        _name = _name.uppercase()
        println(_name)
    }
}
class SubClass(newName : String) : Super() {
    override var name: String = "Mr. $newName"
}
fun main(args: Array<String>) {
    val obj = SubClass("John")
    println(obj.name)
}

运行结果:

INIT block fired with : Name1
NAME1
Mr. John

方案3:避免在父类初始化阶段操作可重写属性(最佳实践)

Kotlin官方文档明确提醒:不要在父类的初始化块、属性初始化器中调用可重写的函数或访问可重写的属性,因为此时子类还没完成初始化,很容易出问题。

我们可以把属性操作逻辑放到一个单独的方法里,等子类初始化完成后再调用:

open class Super {
    open var name : String = "Name1"

    init {
        println("INIT block fired with : $name")
    }

    // 初始化完成后再调用这个方法处理属性
    fun postInit() {
        name = name.uppercase()
        println(name)
    }
}
class SubClass(newName : String) : Super() {
    override var name : String = "Mr. $newName"
}
fun main(args: Array<String>) {
    val obj = SubClass("John")
    obj.postInit()
    println(obj.name)
}

运行结果:

INIT block fired with : Mr. John
MR. JOHN
MR. JOHN

总结

这个问题的核心就是父类初始化时,子类的可重写属性已经被绑定但未完成初始化,导致父类访问到null。遵循Kotlin的初始化最佳实践,尽量避免在父类初始化阶段碰可重写的属性,就能轻松避开这类坑。

内容的提问来源于stack exchange,提问作者user9619889

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:21:34