在 Kotlin 中,lateinit
和 by lazy
都是用于处理延迟初始化的机制,但它们的实现方式、适用场景和特性有显著差异。本文将从原理、用法到实际场景结合示例代码的进行讲解。
一、 lateinit
的特点与使用场景
1、特性
- 修饰可变变量 :仅用于
var
声明。 - 手动初始化:开发者需在适当位置(如生命周期回调)显式初始化。
- 非空类型 :只能用于非空类型(如
String
、View
),不支持基本数据类型 (如Int
、Boolean
)。 - 异常风险 :访问未初始化的变量会抛出
UninitializedPropertyAccessException
。 - 无线程安全:需自行处理多线程环境下的初始化。
2、适用场景
- Android 组件初始化 :如
Activity
/Fragment
中的View
绑定。 - 依赖注入:框架(如 Dagger)在运行时注入的变量。
- 明确生命周期 :确保在使用前完成初始化(如
onCreate()
中初始化)。
3、示例代码
kotlin
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 表达式并缓存结果。
- 支持所有类型 :包括基本数据类型(如
Int
、Boolean
)。 - 线程安全 :默认使用
LazyThreadSafetyMode.SYNCHRONIZED
(安全但略慢),可自定义模式。 - 无异常风险:首次访问时必然初始化。
2、适用场景
- 高开销初始化:如数据库连接、文件读取。
- 单例模式:确保全局唯一实例。
- 条件性初始化:仅在需要时才创建对象。
3、示例代码
kotlin
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 var |
val by lazy |
---|---|---|
变量类型 | 可变 (var ) |
只读 (val ) |
初始化时机 | 手动显式初始化 | 首次访问时自动初始化 |
适用数据类型 | 非空对象类型(不支持基本类型) | 所有类型(包括基本类型) |
线程安全 | 需自行处理 | 默认线程安全(可配置模式) |
异常风险 | 未初始化时抛出异常 | 无(确保首次访问时初始化) |
典型场景 | Android View 绑定、依赖注入 | 单例、高开销资源、延迟计算 |
四、如何选择?
1、选择 lateinit
当:
- 变量需要重新赋值(
var
)。 - 初始化时机明确(如生命周期方法中)。
- 处理非空对象且无法使用
by lazy
(如基本类型不适用)。
2、选择 by lazy
当:
- 变量只需初始化一次且不可变(
val
)。 - 需要延迟初始化直到首次使用,减少启动开销。
- 需要线程安全的延迟初始化。
五、高级用法与注意事项
1、by lazy
的线程模式
kotlin
val data: List<String> by lazy(LazyThreadSafetyMode.NONE) {
// 非线程安全模式,适用于单线程环境
loadExpensiveData()
}
2、lateinit
的初始化检查
kotlin
if (::button.isInitialized) { // 使用反射检查是否初始化
button.text = "Click Me"
}
3、避免陷阱
lateinit
未初始化 :确保在使用前初始化,或通过isInitialized
检查。by lazy
的副作用:Lambda 中的代码应幂等,避免重复执行产生意外结果。
六、总结
lateinit
:适用于可变、生命周期明确的对象,需手动控制初始化。by lazy
:适用于只读、高开销或按需初始化的资源,自动处理线程安全。
根据变量是否需要可变、初始化成本及线程需求,合理选择二者以提升代码效率和安全性。