Kotlin伴生对象子类Object列表调用firstOrNull出现空值问题
这不是预期行为,本质是初始化循环导致的未初始化引用问题
问题原因
出现NullPointerException的核心是Kotlin单例(object)与伴生对象的初始化顺序冲突,具体流程如下:
- 当首次调用
MyBaseClass.getByName("Foo")时,会触发MyBaseClass伴生对象的初始化(因为要访问其list属性)。 - 伴生对象初始化时会执行
val list = listOf(Foo, Bar),此时会尝试获取Foo和Bar这两个单例的引用,触发它们的初始化。 - 由于
Foo继承自MyBaseClass,初始化Foo时需要先完成父类的初始化流程,但此时父类的伴生对象正处于初始化过程中,形成了循环初始化依赖。 - 在这种循环依赖场景下,
Foo的单例实例还未完成初始化,其引用暂时为null,被加入到list中。后续调用it.getName()时,it为null,自然抛出NullPointerException。
而testTwo中先遍历list的操作,会提前触发Foo和Bar的完整初始化(此时伴生对象已经完成初始化,循环依赖被打破),所以后续调用getByName时,list中的元素都是已初始化的有效实例,不会抛出异常。
解决方案
解决这个问题的核心是避免伴生对象初始化时直接触发子类单例的初始化,推荐使用lazy委托延迟初始化list:
abstract class MyBaseClass { abstract fun getName(): String companion object { // 使用lazy延迟初始化list,直到第一次访问时才创建 val list by lazy { listOf(Foo, Bar) } fun getByName(name: String): MyBaseClass? { return list.firstOrNull { it.getName() == name } } } }
这样list的初始化会推迟到第一次被访问时,此时Foo和Bar的单例已经可以正常完成初始化,不会出现未初始化的null引用。
另外也可以将子类单例的定义放在基类之前(如果在同一个文件中),但这种方式依赖文件内的代码顺序,不够健壮,lazy委托是更通用的解决方案。
内容的提问来源于stack exchange,提问作者manabreak
相关产品推荐
相关产品推荐

