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 提供了一种高效、线程安全的延迟初始化机制。

相关推荐
2601_955760071 小时前
如何用 Claude Opus 5 API 批量扩展长尾关键词和文章选题
前端·python·搜索引擎
KaMeidebaby1 小时前
卡梅德生物技术快报|bli亲和力检测gst:告别批量跑胶:BLI实时酶切监测技术加速GST融合蛋白下游流程优化
前端·网络·数据库·人工智能·算法
触底反弹1 小时前
🚀 删了数据刷新又回来?3 组件 × 4 回调 × 3 坑讲透 React 父子通信
前端·javascript·react.js
耳东小鹿3 小时前
对象常用方法
前端
程序员海军3 小时前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
前端·后端·程序员
程序员黑豆3 小时前
鸿蒙应用开发:Stack堆叠组件实战——实现微信消息角标效果
前端·harmonyos
夏幻灵3 小时前
浏览器原理深度解析:从输入 URL 到页面展现的全流程剖析
前端
闲猫4 小时前
Agent工程实践:从WorkFlow到多Agent协作的落地笔记
java·服务器·前端
带娃的IT创业者4 小时前
重新定义前端构建速度:深度解析 SWC 如何用 Rust 颠覆 JavaScript 工具链
前端·javascript·rust·前端构建·swc
happy_0x3f4 小时前
前端应用的离线暂停更新策略
开发语言·前端·php