Kotlin密封类对象初始化顺序为何异常?如何规避NPE?
问题分析与解决方案
你违反的规则
你违反了Kotlin嵌套object与companion object的初始化顺序规则:
- Kotlin的嵌套
object(非companion)是懒加载模式,只有首次被直接或间接引用时才会完成初始化。 companion object的初始化时机是其外部类首次被访问(包括访问嵌套object、调用companion方法等),但如果在companion初始化前提前访问某个嵌套object,会导致初始化顺序交叉,使companion中引用的其他未被访问的object在companion初始化时还未完成实例化,最终在values列表中留下null。
具体到你的代码:当你先访问Animal.Fox时,JVM会触发Fox的初始化流程,但此时Bear、Deer和companion object都未初始化。后续访问Animal.values时触发companion初始化,构建listOf(Bear,Fox,Deer)时会依次触发Bear、Deer的初始化,但Fox的初始化流程因父类与companion的初始化交叉被打断,导致list中的Fox位置暂时为null——即便后续Fox完成初始化,列表里的null也不会自动更新,最终引发NPE。
解决方法
方法1:用lazy延迟初始化values
将values改为延迟初始化,确保它第一次被访问时才构建列表,此时所有嵌套object都会被强制初始化,彻底避免顺序问题:
sealed class Animal(val key: String) { object Bear : Animal("bear") object Fox : Animal("fox") object Deer : Animal("deer") init { println("Animal::init. key : $key") } companion object { // 首次访问values时才构建列表,强制所有object完成初始化 val values by lazy { listOf(Bear, Fox, Deer) } init { println("Animal.Companion::init") } fun valueOf(key: String) = values.firstOrNull { key == it.key } } }
方法2:在companion初始化时主动触发所有object初始化
在companion的init块中主动访问所有嵌套object,强制它们在values初始化前完成实例化:
sealed class Animal(val key: String) { object Bear : Animal("bear") object Fox : Animal("fox") object Deer : Animal("deer") init { println("Animal::init. key : $key") } companion object { val values = listOf(Bear, Fox, Deer) init { // 主动触发所有嵌套object的初始化,确保values无null Bear Fox Deer println("Animal.Companion::init") } fun valueOf(key: String) = values.firstOrNull { key == it.key } } }
方法3:改用enum class(场景允许时)
如果你的密封类只是用来替代枚举的简单场景,直接使用Kotlin原生enum class是最安全的选择——它会自动处理所有实例的初始化顺序,原生支持entries获取所有实例,无需手动维护values列表:
enum class Animal(val key: String) { Bear("bear"), Fox("fox"), Deer("deer"); init { println("Animal::init. key : $key") } companion object { fun valueOf(key: String) = entries.firstOrNull { it.key == key } } }
内容的提问来源于stack exchange,提问作者Agent_L
相关产品推荐
相关产品推荐

