在Kotlin中,by lazy
和 lateinit
都是用于延迟初始化的手段,但它们各自有不同的用法和特点。下面详细讨论它们的作用和区别。
1. by lazy
作用:
by lazy
是一种委托属性,用于延迟初始化一个只读属性。属性在第一次访问时才会被初始化,并且初始化操作只会执行一次。
用法:
语法:
val property: Type by lazy { initializer }
lazy
的默认线程安全模式是 LazyThreadSafetyMode.SYNCHRONIZED
,它确保多线程环境下属性只会被初始化一次。
示例:
代码语言:javascript复制val myValue: String by lazy {
println("Computed only once")
"Hello, World!"
}
// 第一次访问 myValue,会触发初始化代码块执行
println(myValue) // 输出: "Computed only once" 和 "Hello, World!"
// 之后的访问不会重复执行初始化代码块
println(myValue) // 输出: "Hello, World!"
惰性线程安全模式:
LazyThreadSafetyMode.SYNCHRONIZED
:默认值,确保多线程环境下属性只能被初始化一次。LazyThreadSafetyMode.PUBLICATION
:允许多个线程在同一时间初始化,但只使用第一个完成的结果。LazyThreadSafetyMode.NONE
:不进行任何同步,适用于单线程环境。
示例:
代码语言:javascript复制val valueSynchronized: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
"Synchronized"
}
val valuePublication: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
"Publication"
}
val valueNone: String by lazy(LazyThreadSafetyMode.NONE) {
"None"
}
2. lateinit
作用:
lateinit
是一种延迟初始化的关键字,用于延迟初始化一个 var
可变属性。属性类型必须是非空的且不能是原始类型(如 Int
, Double
)。
用法:
语法:
lateinit var property: Type
lateinit
属性不能有自定义的 getter 和 setter,必须在使用之前显式初始化,否则会抛出 UninitializedPropertyAccessException
。
示例:
代码语言:javascript复制lateinit var myValue: String
fun initialize() {
myValue = "Hello, World!"
}
// 使用之前必须显式初始化
initialize()
println(myValue) // 输出: "Hello, World!"
检查初始化:
可以使用 ::property.isInitialized
语法来检查属性是否已经初始化。
if (::myValue.isInitialized) {
println(myValue)
} else {
println("myValue is not initialized")
}
3. 两者对比
特性 | by lazy | lateinit |
---|---|---|
适用类型 | val(只读属性) | var(可变属性) |
初始化时机 | 第一次访问时 | 必须手动初始化 |
线程安全 | 默认线程安全(可选择不同的线程安全模式) | 非线程安全 |
Nullability | 支持不可空类型 | 支持不可空类型(不能用于原始类型) |
属性检查 | 不需要显式检查 | 可以通过 ::property.isInitialized 检查 |
自定义 getter/setter | 不支持 | 不支持 |
使用场景 | 用于只读且惰性初始化的属性 | 用于需要在构造函数之外初始化的可变属性 |
示例场景
by lazy
适用场景:
- 需要惰性初始化不可变的属性。
- 需要线程安全的初始化或者只在单线程中操作。
lateinit
适用场景:
- 需要在构造方法之后初始化的可变属性。
- 需要在某个特定操作时才对属性进行赋值。
总结来说,选择使用 by lazy
还是 lateinit
要依据属性的特性和具体的使用场景。by lazy
更适合不可变的延迟初始化场合,而 lateinit
则适用于在构造方法之后需要手动初始化的可变属性。