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

相关推荐
灿灿121389 分钟前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css
烛阴27 分钟前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript
AntBlack1 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669131 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
尘心cx1 小时前
前端-CSS-day1
前端·css
知否技术1 小时前
前端常说的 SCSS是个啥玩意?一篇文章给你讲的明明白白!
前端·scss
幼儿园技术家1 小时前
Uniapp简易使用canvas绘制分享海报
前端
开开心心就好2 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
全宝2 小时前
🎨前端实现文字渐变的三种方式
前端·javascript·css
yanlele3 小时前
前端面试第 75 期 - 2025.07.06 更新前端面试问题总结(12道题)
前端·javascript·面试