lateinit vs. by lazy:Kotlin变量的延迟初始化哲学

825 阅读2分钟

一、lateinit:手动控制的延迟初始化

lateinit 是一个修饰符,它允许你在声明一个非空可变(var 变量时,不立即为其赋值。它本质上是开发者与编译器之间的一个契约:你告诉编译器,你保证在使用变量之前会手动初始化它。

  • 核心特点

    • 手动初始化:开发者必须在代码中显式地为 lateinit 变量赋值。
    • 编译时绕过:编译器不会对 lateinit 变量进行空值检查,从而避免了在编译时报错。
    • 运行时检查:如果在使用前没有初始化,会抛出 UninitializedPropertyAccessException
  • 适用场景

    • Android 控件绑定ButtonTextView 等控件通常在 ActivityonCreate 方法中通过 findViewById 或 View Binding 来初始化。
    • 依赖注入:在 Dagger 等依赖注入框架中,成员变量通常是在对象创建后才由框架注入的。
  • 如何检查:Kotlin 提供了 ::propertyName.isInitialized 来安全地检查一个 lateinit 变量是否已被初始化,这避免了直接使用而导致的崩溃。


二、by lazy:自动化的惰性初始化

by lazy 是一个属性委托,它使得变量的初始化被推迟到首次访问时。它只执行一次初始化代码块,然后将结果缓存起来,后续访问直接返回缓存值。

  • 核心特点

    • 惰性加载:初始化代码只在第一次访问时执行,这对于高成本的初始化操作(如文件读取、网络请求)非常有用。
    • 不可变(valby lazy 只能用于 val 变量,因为一旦初始化完成,其值就不可更改。
    • 线程安全by lazy 默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED)。它使用锁机制来确保在多线程环境下初始化代码只执行一次。你也可以选择非线程安全模式(NONE)来获得更高的性能。
  • 适用场景

    • 单例模式by lazy 是实现线程安全单例的优雅方式。
    • 配置加载:在首次访问配置对象时,才从文件或网络中读取配置。
    • 高成本对象:如 Retrofit 实例、数据库连接等。

三、对比与选择:手动 vs. 自动

特性lateinitby lazy
关键字lateinit varval by lazy
初始化时机手动赋值,开发者控制首次访问时,自动控制
变量类型var,非空对象类型val,所有类型
线程安全需自行保证默认安全(可配置)
使用风险未初始化崩溃初始化代码异常崩溃
  • 选择 lateinit:当你需要一个可变的变量,并且其初始化时机由外部环境(如 Activity 生命周期)决定时。
  • 选择 by lazy:当你需要一个不可变的、延迟加载的变量,并且其初始化逻辑可以被封装在一个代码块中时。