lazy
委托是 Kotlin 的一种属性委托,用于实现延迟初始化。所谓属性委托,就是将属性的 getter 和 setter 操作委托给其他对象来处理。lazy
委托允许我们在第一次访问属性时才进行初始化,后续访问直接返回已缓存的值。这种机制可以提高性能,避免不必要的开销,尤其是在处理开销较大的对象时。
使用
kotlin
val lazyVal: String by lazy {
println("Computed")
"Lazy"
}
println(lazyVal) // 输出: Computed, Lazy
println(lazyVal) // 输出: Lazy(不再计算)
lazyVal
使用 lazy
委托延迟初始化,只有首次访问时执行初始化块 { println("Computed"); "Lazy" }
,后续访问直接返回缓存值。
底层实现原理
lazy
是一个高阶函数,定义在 Kotlin 标准库中:
kotlin
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
参数 initializer: () -> T
是一个无参 Lambda,返回类型为 T
(此处 T
是 String
)。返回 Lazy<T>
接口实例,具体实现是 SynchronizedLazyImpl
。
Lazy<T>
接口:
kotlin
public interface Lazy<out T> {
val value: T // 获取委托值
fun isInitialized(): Boolean // 检查是否已初始化
}
这里把 lazyVal
的初始化逻辑封装在一个 Lazy
对象中,后续访问 lazyVal
时,实际上是访问这个 Lazy
对象的 value
属性。
lazy
默认使用 SynchronizedLazyImpl
,其代码如下:
kotlin
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile // 确保多线程环境下的可见性
private var _value: Any? = UNINITIALIZED_VALUE
// final field to ensure safe publication of 'SynchronizedLazyImpl' itself through
// var lazy = lazy() {}
private val lock = lock ?: this // 使用自身作为锁
override val value: T
get() {
val _v1 = _value
// 如果 _value 已经初始化,直接返回
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) { // 再次检查,避免多线程下重复初始化
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!() // 调用 initializer 初始化值
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
_value
初始为 UNINITIALIZED_VALUE
(哨兵对象)。首次访问 value
时,检查 _value
是否未初始化,若是则调用 initializer()
(即 { println("Computed"); "Lazy" }
)。
使用 @Volatile
确保 _value
的可见性。synchronized(lock)
实现双重检查锁(Double-Checked Locking),保证多线程环境下初始化只执行一次。
初始化后,_value
保存结果("Lazy"
),后续访问直接返回,无需再次调用 initializer
。
Kotlin 编译器将 lazyVal
的访问转换为对 Lazy
对象的调用。简化后的字节码(伪代码):
kotlin
// 编译前
val lazyVal: String by lazy { println("Computed"); "Lazy" }
// 编译后(大致等效)
private val lazyVal$delegate: Lazy<String> = lazy { println("Computed"); "Lazy" }
val lazyVal: String
get() = lazyVal$delegate.value
第一次 lazyVal
访问调用 lazyVal$delegate.value
。SynchronizedLazyImpl
执行 initializer
,打印 Computed
,返回 "Lazy"
,并缓存。
第二次访问直接返回缓存的 _value
("Lazy"
),无 initializer
调用。
lazy
支持不同线程安全模式,通过 LazyThreadSafetyMode
参数:
kotlin
val lazyVal: String by lazy(LazyThreadSafetyMode.NONE) { "Lazy" } // 无同步,单线程使用
val lazyValPub: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "Lazy" } // 允许多线程初始化,最终一致
默认 SYNCHRONIZED
(如上述代码)适合多线程场景。
总结
属性委托(by
)将 get
操作转发给 Lazy
。lazy
委托通过 SynchronizedLazyImpl
实现延迟初始化。使用双重检查锁确保线程安全,首次访问执行 initializer
,后续返回缓存值。编译器将 lazyVal
转换为 Lazy
对象的 value
访问。
lazy
适合昂贵初始化的场景(如数据库连接、配置加载)。注意线程安全模式的选择(默认 SYNCHRONIZED
适合多数场景)。
通过 lazy
委托,Kotlin 提供了一种高效、线程安全的延迟初始化机制。