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:点击查看完整代码