lateinit
和 lazy
详解
核心区别
特性 |
lateinit |
lazy |
类型 |
可变属性修饰符 var |
不可变属性委托 val |
初始化时机 |
手动显式初始化,随时可变 |
首次访问时自动初始化,之后不可变 |
空安全 |
非空类型,但初始值可缺失 |
非空类型,保证有值 |
适用类型 |
不能用于基本类型(Int, Boolean 等) |
可用于任何类型 |
线程安全 |
不保证线程安全 |
默认线程安全SYNCHRONIZED 模式 |
检查机制 |
使用前需确保已初始化,否则抛异常 |
自动处理初始化,不会抛出未初始化异常 |
lateinit
核心用法
class User {
lateinit var name: String
fun initialize() {
name = "John"
}
fun greet() {
if (::name.isInitialized) {
println("Hello, $name")
}
}
}
最佳使用场景:
- 依赖注入
Activity/Fragment
中的视图绑定
- 单元测试的
setUp
方法中
- 需要推迟初始化但之后可能需要修改的属性
lazy
核心用法
class User {
val name: String by lazy {
println("Computing name...")
"John"
}
}
val expensiveData: List<Data> by lazy(LazyThreadSafetyMode.PUBLICATION) {
loadDataFromDatabase()
}
线程安全模式:
SYNCHRONIZED
:默认模式,线程安全,只执行一次初始化
PUBLICATION
:多线程可能执行多次,但只有第一个结果被使用
NONE
:不保证线程安全,适用于单线程环境,性能最好
最佳使用场景:
- 计算开销大的属性
- 需要根据条件计算的只读属性
- 单例模式实现
- 配置项和缓存数据
核心实现原理
lateinit
- 在字节码级别,不为属性分配默认值
- 访问前不进行空检查
- 使用前若未初始化,抛出
UninitializedPropertyAccessException
lazy
- 内部使用
SynchronizedLazyImpl
等实现类
- 持有一个初始化器函数和一个存储结果的
AtomicReference
- 首次访问时执行初始化器并缓存结果
如何选择
- 如果属性需要在初始化后修改,使用
lateinit var
- 如果属性是只读的且可以延迟计算,使用l
azy val
- 如果处理基本类型,只能使用
lazy
- 如果在多线程环境中,优先考虑
lazy