一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法

552 阅读3分钟

在 Kotlin 中,lateinit 和 by lazy 都是用于处理延迟初始化的机制,但它们的实现方式、适用场景和特性有显著差异。本文将从原理、用法到实际场景结合示例代码的进行讲解。

一、 lateinit 的特点与使用场景

1、特性

  • 修饰可变变量:仅用于 var 声明。
  • 手动初始化:开发者需在适当位置(如生命周期回调)显式初始化。
  • 非空类型:只能用于非空类型(如 StringView),不支持基本数据类型(如 IntBoolean)。
  • 异常风险:访问未初始化的变量会抛出 UninitializedPropertyAccessException
  • 无线程安全:需自行处理多线程环境下的初始化。

2、适用场景

  • Android 组件初始化:如 Activity/Fragment 中的 View 绑定。
  • 依赖注入:框架(如 Dagger)在运行时注入的变量。
  • 明确生命周期:确保在使用前完成初始化(如 onCreate() 中初始化)。

3、示例代码

class MyActivity : AppCompatActivity() {
    private lateinit var button: Button // 非空,延迟初始化

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button = findViewById(R.id.btn_submit) // 手动初始化
        button.setOnClickListener { /* ... */ }
    }
}

二、 by lazy 的特点与使用场景

1、特性

  • 修饰只读变量:仅用于 val 声明。
  • 自动初始化:首次访问时执行 Lambda 表达式并缓存结果。
  • 支持所有类型:包括基本数据类型(如 IntBoolean)。
  • 线程安全:默认使用 LazyThreadSafetyMode.SYNCHRONIZED(安全但略慢),可自定义模式。
  • 无异常风险:首次访问时必然初始化。

2、适用场景

  • 高开销初始化:如数据库连接、文件读取。
  • 单例模式:确保全局唯一实例。
  • 条件性初始化:仅在需要时才创建对象。

3、示例代码

class MyService {
    // 高开销资源,首次访问时初始化
    private val database: Database by lazy {
        Database.connect("jdbc:mysql://localhost:3306/mydb")
    }

    fun queryData() {
        val result = database.query("SELECT * FROM users") // 首次调用时初始化
        // ...
    }
}

三、核心对比表格

特性lateinit varval by lazy
变量类型可变 (var)只读 (val)
初始化时机手动显式初始化首次访问时自动初始化
适用数据类型非空对象类型(不支持基本类型)所有类型(包括基本类型)
线程安全需自行处理默认线程安全(可配置模式)
异常风险未初始化时抛出异常无(确保首次访问时初始化)
典型场景Android View 绑定、依赖注入单例、高开销资源、延迟计算

四、如何选择?

1、选择 lateinit 当:

  • 变量需要重新赋值(var)。
  • 初始化时机明确(如生命周期方法中)。
  • 处理非空对象且无法使用 by lazy(如基本类型不适用)。

2、选择 by lazy 当:

  • 变量只需初始化一次且不可变(val)。
  • 需要延迟初始化直到首次使用,减少启动开销。
  • 需要线程安全的延迟初始化。

五、高级用法与注意事项

1、by lazy 的线程模式

val data: List<String> by lazy(LazyThreadSafetyMode.NONE) {
    // 非线程安全模式,适用于单线程环境
    loadExpensiveData()
}

2、lateinit 的初始化检查

if (::button.isInitialized) { // 使用反射检查是否初始化
    button.text = "Click Me"
}

3、避免陷阱

  • lateinit 未初始化:确保在使用前初始化,或通过 isInitialized 检查。
  • by lazy 的副作用:Lambda 中的代码应幂等,避免重复执行产生意外结果。

六、总结

  • lateinit:适用于可变、生命周期明确的对象,需手动控制初始化。
  • by lazy:适用于只读、高开销或按需初始化的资源,自动处理线程安全。

根据变量是否需要可变、初始化成本及线程需求,合理选择二者以提升代码效率和安全性。

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. 一文带你吃透接口(Interface)结合 @AutoService 与 ServiceLoader 详解
  4. 一文带你吃透Android中显示Intent与隐式Intent的区别
  5. 一文带你吃透Android View绘制流程与原理详解