Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

惰性初始化

到目前为止,你学习了两种初始化属性的方式。

  1. 在定义点或构造函数中存储初始值。
  2. 定义一个自定义的 getter,每次访问时计算属性的值。

这个章节探讨了第三种用例:昂贵的初始化,你可能不需要立即进行,甚至可能永远都不需要。例如:

  • 复杂且耗时的计算
  • 网络请求
  • 数据库访问

这可能会产生两个问题:

  1. 长时间的应用程序启动时间。
  2. 为从未使用过的属性执行不必要的工作,或者为具有延迟访问的属性执行工作。

这种情况经常发生,Kotlin 包含了一个内置的解决方案。lazy 属性是在第一次使用时初始化的,而不是在创建时初始化。如果我们从不使用 lazy 属性,它就永远不会执行那个昂贵的初始化。

lazy 属性的概念在 Kotlin 中并不是唯一的。无论编程语言是否直接提供支持,惰性都可以在其他语言中实现。Kotlin 使用 属性委托 提供了一种一致且可识别的惯用法来处理这类属性。对于 lazy 属性,by 后面跟着对 lazy() 的调用:

val lazyProperty by lazy { initializer }

lazy() 接受一个包含初始化逻辑的 lambda。像往常一样,lambda 中的最后一个表达式将成为结果,然后赋值给属性:

// LazyInitialization/LazySyntax.kt
package lazyinitialization
import atomictest.*

val idle: String by lazy {
  trace("Initializing 'idle'")
  "I'm never used"
}

val helpful: String by lazy {
  trace("Initializing 'helpful'")
  "I'm helping!"
}

fun main() {
  trace(helpful)
  trace eq """
    Initializing 'helpful'
    I'm helping!
  """
}

idle 属性不会被初始化,因为从未访问过它。

请注意,helpfulidle 都是 val。如果没有 lazy 初始化,你将被迫将它们定义为 var,从而产生不太可靠的代码。

我们可以通过为 Int 属性实现与 lazy 初始化相同的行为,来看看 lazy 初始化为我们做了哪些工作:

// LazyInitialization/LazyInt.kt
package lazyinitialization
import atomictest.*

class LazyInt(val init: () -> Int) {
  private var helper: Int? = null
  val value: Int
    get() {
      if (helper == null)
        helper = init()
      return helper!!
    }
}

fun main() {
  val later = LazyInt {
    trace("Initializing 'later'")
    5
  }
  trace("First 'value' access:")
  trace(later.value)
  trace("Second 'value' access:")
  trace(later.value)
  trace eq """
    First 'value' access:
    Initializing 'later'
    5
    Second 'value' access:
    5
  """
}

value 属性不存储值,而是具有从 helper 属性中检索值的 getter。这与 Kotlin 为 lazy 生成的代码类似。

现在我们可以比较三种初始化属性的方式——在定义点、使用 getter,以及使用 lazy 初始化:

// LazyInitialization/PropertyOptions.kt
package lazyinitialization
import atomictest.trace

fun compute(i: Int): Int {
  trace("Compute $i")
  return i
}

object Properties {
  val atDefinition = compute(1)
  val getter
    get() = compute(2)
  val lazyInit by lazy { compute(3) }
  val never by lazy { compute(4) }
}

fun main() {
  listOf(
    Properties::atDefinition,
    Properties::getter,
    Properties::lazyInit
  ).forEach {
    trace("${it.name}:")
    trace("${it.get()}")
    trace("${it.get()}")
  }
  trace eq """
    Compute 1
    atDefinition:
    1
    1
    getter:
    Compute 2
    2
    Compute 2
    2
    lazyInit:
    Compute 3
    3
    3
  """
}
  • atDefinition 在创建 Properties 实例时初始化。
  • “Compute 1” 出现在 “atDefinition:” 之前,显示初始化发生在任何访问之前。
  • getter 每次访问时都会重新计算。两次访问属性时都会出现一次 “Compute 2”。
  • lazyInit 的初始化值仅在首次访问时计算。如果不访问该属性,初始化永远不会发生,注意在跟踪中从未出现 “Compute 4”。

练习和解答可在 www.AtomicKotlin.com 找到。