Kotlin继承父类时初始化块触发属性为空,抛出类型转换异常
解决Kotlin子类继承时父类初始化块触发属性为null的类型转换异常
这个坑我之前踩过!本质是Kotlin类初始化顺序和可重写属性绑定机制导致的,咱们一步步拆解:
问题根源
当你创建SubClass实例时,初始化顺序是这样的:
- 先执行父类
Super的初始化逻辑,但注意——子类重写的var属性会在父类初始化阶段就被绑定到子类的实现上,此时子类的name还没完成初始化(子类的属性初始化要等父类完全初始化完才会执行)。 - 所以父类初始化块里访问的
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
相关产品推荐
相关产品推荐

