1. 概述

在 Kotlin 中,我们有多种方式可以实现变量或属性的延迟初始化(lazy-initialize)甚至后期初始化(late-initialize)。

本文将重点讲解如何判断一个 lateinit 变量是否已经被初始化。这在实际开发中非常实用,尤其是在使用依赖注入框架或编写测试代码时,避免因访问未初始化变量而引发运行时异常。

2. Lateinit 基础知识

通过 lateinit 关键字,我们可以推迟变量的初始化时机。这种机制特别适用于 Spring 等依赖注入场景,或是单元测试中由测试框架负责注入实例的情况。

但有个致命坑点⚠️:如果访问了一个尚未初始化的 lateinit 变量,Kotlin 会直接抛出 UninitializedPropertyAccessException 异常

示例代码如下:

private lateinit var answer: String

@Test(expected = UninitializedPropertyAccessException::class)
fun givenLateInit_WhenNotInitialized_ShouldThrowAnException() {
    answer.length
}

这个异常明确告诉你:该变量还没赋值就被调用了。

解决方案:使用 isInitialized 方法

从 Kotlin 1.2 开始,可以通过属性引用(property reference)调用 isInitialized 来安全地检查初始化状态:

assertFalse { this::answer.isInitialized }

这里的 :: 语法用于获取属性引用。一旦完成赋值,结果就会变为 true

answer = "42"
assertTrue { this::answer.isInitialized }

⚠️ 注意:isInitialized 是 Kotlin 1.2+ 才支持的功能,老版本无法使用。如果你还在用旧版编译器,请先升级。

3. isInitialized 的访问权限问题

虽然 isInitialized 很好用,但在跨类访问时容易踩坑 ❌。本节我们将分析常见问题并给出解决方案。

3.1 问题复现

假设我们有三个类:

  • Class1:最底层的数据类
  • Class2:持有一个 lateinit var class1: Class1
  • Class3:持有一个 lateinit var class2: Class2

定义如下:

class Class1

class Class2 {
    lateinit var class1: Class1
}

class Class3 {
    lateinit var class2: Class2
}

现在想在 Class3 中写一个方法,用来判断整个链路是否都已完成初始化:

fun isInitializedDirectAccess(): Boolean {
    return ::class2.isInitialized && class2::class1.isInitialized
}

看起来逻辑很清晰,但实际上这段代码 编译不过 ❌!

报错信息为:

Backing field of 'var class1: Class1' is not accessible at this point

原因在于:**isInitialized 只能在声明该属性的类内部、外层类或同一文件的顶层访问**。而 class2::class1 是在 Class3 中尝试访问 Class2 的私有 backing field,属于越权操作。

3.2 解决方案:封装判断逻辑

要绕过这个限制,核心思路是——isInitialized 判断封装成 public 方法暴露出去

我们在 Class2 中添加一个公开方法:

fun isInitialized(): Boolean {
    return ::class1.isInitialized
}

然后在 Class3 中调用它:

fun isInitialized(): Boolean {
    return ::class2.isInitialized && class2.isInitialized()
}

这样就避开了跨类访问 backing field 的限制。

✅ 验证正确性:

val class1 = Class1()
val class2 = Class2()
val class3 = Class3()

class2.class1 = class1
class3.class2 = class2

assertTrue(class2.isInitialized())      // true
assertTrue(class3.isInitialized())      // true

✅ 再验证未初始化情况:

val class1 = Class1()
val class2 = Class2()
val class3 = Class3()

assertFalse(class2.isInitialized())     // false
assertFalse(class3.isInitialized())     // false

完美!逻辑成立,且无编译错误。

4. 总结

  • ✅ 使用 ::propertyName.isInitialized 可以安全判断 lateinit 变量是否已初始化(Kotlin 1.2+)
  • ❌ 不要在外部类直接访问其他类的 ::lateinitProperty.isInitialized,会因访问权限导致编译失败
  • ✅ 正确做法是:在目标类中提供 isInitialized() 方法进行封装,通过接口方式对外暴露状态

这套模式在构建复杂对象图、DI 容器管理、组件生命周期检测等场景下非常实用。

示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-2


原始标题:Checking Whether a lateinit var Is Initialized in Kotlin