一、lateinit:手动控制的延迟初始化
lateinit 是一个修饰符,它允许你在声明一个非空可变(var) 变量时,不立即为其赋值。它本质上是开发者与编译器之间的一个契约:你告诉编译器,你保证在使用变量之前会手动初始化它。
-
核心特点:
- 手动初始化:开发者必须在代码中显式地为
lateinit变量赋值。 - 编译时绕过:编译器不会对
lateinit变量进行空值检查,从而避免了在编译时报错。 - 运行时检查:如果在使用前没有初始化,会抛出
UninitializedPropertyAccessException。
- 手动初始化:开发者必须在代码中显式地为
-
适用场景:
- Android 控件绑定:
Button、TextView等控件通常在Activity的onCreate方法中通过findViewById或 View Binding 来初始化。 - 依赖注入:在 Dagger 等依赖注入框架中,成员变量通常是在对象创建后才由框架注入的。
- Android 控件绑定:
-
如何检查:Kotlin 提供了
::propertyName.isInitialized来安全地检查一个lateinit变量是否已被初始化,这避免了直接使用而导致的崩溃。
二、by lazy:自动化的惰性初始化
by lazy 是一个属性委托,它使得变量的初始化被推迟到首次访问时。它只执行一次初始化代码块,然后将结果缓存起来,后续访问直接返回缓存值。
-
核心特点:
- 惰性加载:初始化代码只在第一次访问时执行,这对于高成本的初始化操作(如文件读取、网络请求)非常有用。
- 不可变(
val) :by lazy只能用于val变量,因为一旦初始化完成,其值就不可更改。 - 线程安全:
by lazy默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED)。它使用锁机制来确保在多线程环境下初始化代码只执行一次。你也可以选择非线程安全模式(NONE)来获得更高的性能。
-
适用场景:
- 单例模式:
by lazy是实现线程安全单例的优雅方式。 - 配置加载:在首次访问配置对象时,才从文件或网络中读取配置。
- 高成本对象:如
Retrofit实例、数据库连接等。
- 单例模式:
三、对比与选择:手动 vs. 自动
| 特性 | lateinit | by lazy |
|---|---|---|
| 关键字 | lateinit var | val by lazy |
| 初始化时机 | 手动赋值,开发者控制 | 首次访问时,自动控制 |
| 变量类型 | var,非空对象类型 | val,所有类型 |
| 线程安全 | 需自行保证 | 默认安全(可配置) |
| 使用风险 | 未初始化崩溃 | 初始化代码异常崩溃 |
- 选择
lateinit:当你需要一个可变的变量,并且其初始化时机由外部环境(如Activity生命周期)决定时。 - 选择
by lazy:当你需要一个不可变的、延迟加载的变量,并且其初始化逻辑可以被封装在一个代码块中时。