Kotlin lazy 委托的底层实现原理
代码概览
1 | val lazyVal: String by lazy { |
- 功能:
lazyVal
使用lazy
委托延迟初始化,只有首次访问时执行初始化块{ println("Computed"); "Lazy" }
,后续访问直接返回缓存值。 - 输出:
- 第一次
println(lazyVal)
:打印Computed
(初始化执行),然后打印Lazy
。 - 第二次
println(lazyVal)
:直接打印Lazy
(无Computed
,因为值已缓存)。
- 第一次
底层实现原理
Kotlin 的 lazy
委托是标准库中的一种属性委托,通过
by lazy { ... }
实现延迟初始化。其底层依赖于
Lazy<T>
接口和线程安全的实现类(默认使用
SynchronizedLazyImpl
)。以下是实现细节:
1. lazy
委托的定义
lazy
是一个高阶函数,定义在 Kotlin 标准库中:1
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
- 参数
initializer: () -> T
是一个无参 Lambda,返回类型为T
(此处T
是String
)。 - 返回
Lazy<T>
接口实例,具体实现是SynchronizedLazyImpl
。
- 参数
Lazy<T>
接口:1
2
3
4public interface Lazy<out T> {
val value: T
fun isInitialized(): Boolean
}value
属性获取委托值。isInitialized()
检查是否已初始化。
2. 属性委托机制
by lazy { ... }
使用 Kotlin 的属性委托(Property Delegation):lazy
函数返回的Lazy<String>
对象实现了getValue
方法,符合委托协议:1
operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
当访问
lazyVal
时,Kotlin 编译器将调用委托对象的getValue
,即Lazy
的value
属性。
3.
SynchronizedLazyImpl
的实现
lazy
默认使用SynchronizedLazyImpl
,其核心逻辑如下(简化伪代码):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private class SynchronizedLazyImpl<out T>(private val initializer: () -> T) : Lazy<T> {
private var _value: Any? = UNINITIALIZED_VALUE // 初始标记
private val lock = this // 使用自身作为锁
override val value: T
get() {
if (_value === UNINITIALIZED_VALUE) {
synchronized(lock) {
if (_value === UNINITIALIZED_VALUE) {
_value = initializer.invoke() // 执行初始化
}
}
}
return _value as T
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}关键点:
- 延迟初始化:
_value
初始为UNINITIALIZED_VALUE
(哨兵对象)。- 首次访问
value
时,检查_value
是否未初始化,若是则调用initializer()
(即{ println("Computed"); "Lazy" }
)。
- 线程安全:
- 使用
@Volatile
确保_value
的可见性。 synchronized(lock)
实现双重检查锁(Double-Checked Locking),保证多线程环境下初始化只执行一次。
- 使用
- 缓存结果:
- 初始化后,
_value
保存结果("Lazy"
),后续访问直接返回,无需再次调用initializer
。
- 初始化后,
- 延迟初始化:
4. 编译后的代码
Kotlin 编译器将
lazyVal
的访问转换为对Lazy
对象的调用。简化后的字节码(伪代码):1
2
3
4
5
6
7// 编译前
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
调用。
- 直接返回缓存的
- 第一次
代码执行流程
- 初始化:
lazyVal
定义时,创建一个SynchronizedLazyImpl
实例,保存initializer = { println("Computed"); "Lazy" }
。_value
初始为UNINITIALIZED_VALUE
。
- 第一次访问:
println(lazyVal)
触发lazyVal$delegate.value
。- 检查
_value
未初始化,进入synchronized
块。 - 执行
initializer
,打印Computed
,设置_value = "Lazy"
。 - 返回
"Lazy"
,println
输出Computed
和Lazy
。
- 第二次访问:
println(lazyVal)
再次调用value
。_value
已为"Lazy"
,直接返回。println
输出Lazy
,无Computed
。
其他懒加载模式
lazy
支持不同线程安全模式,通过LazyThreadSafetyMode
参数:1
2val lazyVal: String by lazy(LazyThreadSafetyMode.NONE) { "Lazy" } // 无同步,单线程使用
val lazyValPub: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "Lazy" } // 允许多线程初始化,最终一致- 默认
SYNCHRONIZED
(如上述代码)适合多线程场景。
代码示例(扩展)
1 | val lazyVal: String by lazy { |
输出:
1
2
3
4
5Before access
Computed
Lazy
Lazy
Is initialized: true说明:
::lazyVal.isInitialized()
检查是否已初始化,底层调用lazyVal$delegate.isInitialized()
。- 确认第二次访问不触发
initializer
。
总结
- 底层实现:
lazy
委托通过SynchronizedLazyImpl
实现延迟初始化。- 使用双重检查锁确保线程安全,首次访问执行
initializer
,后续返回缓存值。 - 编译器将
lazyVal
转换为Lazy
对象的value
访问。
- 关键机制:
- 属性委托(
by
)将get
操作转发给Lazy
。 @Volatile
和synchronized
保证多线程安全。_value
缓存初始化结果,优化性能。
- 属性委托(
- 实践:
lazy
适合昂贵初始化的场景(如数据库连接、配置加载)。- 注意线程安全模式的选择(默认
SYNCHRONIZED
适合多数场景)。
通过 lazy
委托,Kotlin
提供了一种高效、线程安全的延迟初始化机制,广泛用于 Android 和其他 Kotlin
项目。