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