lateinit 和 lazy 详解
核心区别
| 特性 | lateinit | lazy |
|---|
| 类型 | 可变属性修饰符 var | 不可变属性委托 val |
| 初始化时机 | 手动显式初始化,随时可变 | 首次访问时自动初始化,之后不可变 |
| 空安全 | 非空类型,但初始值可缺失 | 非空类型,保证有值 |
| 适用类型 | 不能用于基本类型(Int, Boolean等) | 可用于任何类型 |
| 线程安全 | 不保证线程安全 | 默认线程安全SYNCHRONIZED模式 |
| 检查机制 | 使用前需确保已初始化,否则抛异常 | 自动处理初始化,不会抛出未初始化异常 |
lateinit 核心用法
class User {lateinit var name: Stringfun 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