惰性初始化
到目前为止,你学习了两种初始化属性的方式。
- 在定义点或构造函数中存储初始值。
- 定义一个自定义的 getter,每次访问时计算属性的值。
这个章节探讨了第三种用例:昂贵的初始化,你可能不需要立即进行,甚至可能永远都不需要。例如:
- 复杂且耗时的计算
- 网络请求
- 数据库访问
这可能会产生两个问题:
- 长时间的应用程序启动时间。
- 为从未使用过的属性执行不必要的工作,或者为具有延迟访问的属性执行工作。
这种情况经常发生,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
属性不会被初始化,因为从未访问过它。
请注意,helpful
和 idle
都是 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 找到。