Kotlin lazy 委托的底层实现原理

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(此处 TString)。返回 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.valueSynchronizedLazyImpl 执行 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 操作转发给 Lazylazy 委托通过 SynchronizedLazyImpl 实现延迟初始化。使用双重检查锁确保线程安全,首次访问执行 initializer,后续返回缓存值。编译器将 lazyVal 转换为 Lazy 对象的 value 访问。

lazy 适合昂贵初始化的场景(如数据库连接、配置加载)。注意线程安全模式的选择(默认 SYNCHRONIZED 适合多数场景)。

通过 lazy 委托,Kotlin 提供了一种高效、线程安全的延迟初始化机制。

相关推荐
江城开朗的豌豆4 小时前
TypeScript和JavaScript到底有什么区别?
前端·javascript
鸡吃丸子5 小时前
初识Docker
运维·前端·docker·容器
老华带你飞5 小时前
学生请假管理|基于springboot 学生请假管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·spring
前端不太难5 小时前
如何给 RN 项目设计「不会失控」的导航分层模型
前端·javascript·架构
用户4099322502125 小时前
Vue3中v-show如何通过CSS修改display属性控制条件显示?与v-if的应用场景该如何区分?
前端·javascript·vue.js
不会聊天真君6475 小时前
CSS3(Web前端开发笔记第二期)
前端·笔记·css3
discode6 小时前
【开源项目技术分享】@host-navs 站导,一个简洁高效的网站链接导航工具站
前端
PieroPC6 小时前
Nicegui 3.4.0 可以缩小组件之间的间距 label botton input textarea
前端