1. 引言

在 Kotlin 中,Lazy Initialization(延迟初始化)Late Initialization(滞后初始化) 经常被混淆。表面上看,它们都表示类的属性不会在声明时立即初始化,而是在后续某个时刻才赋值。但它们背后的机制和适用场景其实完全不同。

本文将从原理、语法、适用场景等方面详细对比 Lazy 与 Late 初始化,帮助你准确理解它们的差异,避免在项目中“踩坑”。


2. Lazy Initialization(延迟初始化)

Lazy 初始化是 Kotlin 提供的一种委托属性(Delegated Property)机制,通过标准库中的 lazy { } 函数实现。

✅ 语法示例:

val lazyValue by lazy {
    println("Only now the field is initialized")
    18
}

🔍 实现原理

  • lazy { } 返回的是一个 Lazy<T> 类型的对象。
  • 默认使用的是 kotlin.SynchronizedLazyImpl,内部通过 synchronized 实现线程安全。
  • 第一次访问时执行初始化逻辑,后续访问直接返回缓存值,性能高效。

⚠️ 注意事项

  • 只能用于 val(不可变属性),因为 Lazy<T> 没有 setter
  • 支持基本类型(如 Int, String 等)。
  • 天然线程安全,适合用于初始化耗时、只读的属性,如配置、单例等。

3. Late Initialization(滞后初始化)

Late Initialization 是 Kotlin 提供的一个语言关键字,使用 lateinit var 声明。

✅ 语法示例:

class LateinitSample {
    lateinit var lateValue: ValueType
}

⚠️ 限制条件

  • 必须是 var(可变属性)。
  • 不能是基本类型(primitive types),只能是对象引用类型。
  • 编译器不会检查是否已初始化,**运行时访问未初始化的 lateinit 属性会抛出 UninitializedPropertyAccessException**。

✅ 正确使用方式:

class LateinitSample {
    lateinit var lateValue: ValueType

    fun initBasedOnEnvironment(env: Map<String, String>) {
        lateValue = ValueType(env.toString())
    }
}

val sample = LateinitSample().apply { 
    initBasedOnEnvironment(mapOf("key" to "value"))
}
sample.lateValue // 不抛异常

⚠️ 踩坑提醒

  • 容易忘记初始化,尤其在复杂业务逻辑中,容易导致运行时崩溃。
  • 难以维护,尤其是在多人协作项目中,其他开发者可能不清楚你“承诺”了什么。
  • 建议只在特定框架回调中使用,如 Android 的 onCreate()、Spring 的 @PostConstruct 等。

4. Lazy 与 Late 初始化对比总结

特性 Lazy Initialization Late Initialization
关键字 by lazy { } lateinit var
属性类型 val(只读) var(可变)
支持类型 基本类型和对象类型 仅对象类型
线程安全 是(默认实现) 否(需手动控制)
编译时检查 是(必须赋值) 否(靠开发者承诺)
运行时异常 是(未初始化访问会抛异常)
适用场景 初始化耗时、只读属性 框架回调、延迟赋值

5. 总结

虽然 Lazy 和 Late 初始化都能实现“延迟赋值”的效果,但它们本质完全不同:

  • Lazy 初始化是通过委托机制实现的延迟加载,线程安全且编译器严格检查,推荐使用。
  • Late 初始化是语言层面的“承诺式”初始化,容易出错,应谨慎使用。

在实际开发中,优先推荐使用 by lazy {},只有在框架强制要求或初始化时机无法确定时,才考虑使用 lateinit

示例代码已整理在 GitHub:点击查看完整代码


原始标题:Lazy Initialization vs Late Initialization in Kotlin