延迟初始化
有时候,你想在创建类的实例后才初始化属性,但是想在一个单独的成员函数中进行初始化,而不是使用
lazy
。
例如,一个框架或库可能要求在特殊函数中进行初始化。如果你扩展了该库的类,你可以提供自己的特殊函数的实现。
考虑一个具有 setUp()
函数来初始化实例的 Bag
接口:
// LateInitialization/Bag.kt
package lateinitialization
interface Bag {
fun setUp()
}
假设我们想要重用一个创建和操作 Bag
的库,并保证调用了 setUp()
。该库要求在 setUp()
中进行子类初始化,而不是在构造函数中:
// LateInitialization/Suitcase.kt
package lateinitialization
import atomictest.eq
class Suitcase : Bag {
private var items: String? = null
override fun setUp() {
items = "socks, jacket, laptop"
}
fun checkSocks(): Boolean =
items?.contains("socks") ?: false
}
fun main() {
val suitcase = Suitcase()
suitcase.setUp()
suitcase.checkSocks() eq true
}
Suitcase
通过覆盖 setUp()
来初始化 items
。然而,我们不能仅仅将 items
定义为一个 String
—— 如果这样做,我们必须在构造函数中提供一个非空的初始化值。使用空字符串等存根值是一个不好的做法,因为你永远不知道它是否实际被初始化过。null
表示尚未初始化。
将 items
定义为可为空的 String?
意味着我们必须在所有成员函数中检查 null
,如 checkSocks()
。然而,我们知道我们正在重用的库通过调用 setUp()
来初始化 items
,因此 null
检查不应该是必要的。
lateinit
属性修饰符可以解决这个问题 —— 在创建 BetterSuitcase
实例后,我们初始化 items
:
// LateInitialization/BetterSuitcase.kt
package lateinitialization
import atomictest.eq
class BetterSuitcase : Bag {
lateinit var items: String
override fun setUp() {
items = "socks, jacket, laptop"
}
fun checkSocks() = "socks" in items
}
fun main() {
val suitcase = BetterSuitcase()
suitcase.setUp()
suitcase.checkSocks() eq true
}
将这个版本的 checkSocks()
与 Suitcase.kt
中的版本进行比较。lateinit
意味着 items
被安全地定义为非空属性。
lateinit
可以用于类体内的属性、顶层属性或局部 var
。
限制:
lateinit
只能用于var
属性,不能用于val
。- 属性必须是非空类型。
- 属性不能是原始类型。
abstract
类或interface
中的abstract
属性不允许使用lateinit
。- 具有自定义
get()
或set()
的属性不允许使用lateinit
。
如果你忘记初始化这样的属性会发生什么?你不会得到编译时的错误或警告,因为初始化逻辑可能很复杂,并且可能依赖于 Kotlin 无法监视的其他属性:
// LateInitialization/FaultySuitcase.kt
package lateinitialization
import atomictest.*
class FaultySuitcase : Bag {
lateinit var items: String
override fun setUp() {}
fun checkSocks() = "socks" in items
}
fun main() {
val suitcase = FaultySuitcase()
suitcase.setUp()
capture {
suitcase.checkSocks()
} eq
"UninitializedPropertyAccessException" +
": lateinit property items " +
"has not been initialized"
}
这个运行时异常提供了足够的细节,让你能够轻松地发现并修复问题。通常情况下,跟踪由于 null
指针异常引起的错误要困难得多。
.isInitialized
可以告诉你一个 lateinit
属性是否被初始化。该属性必须在当前作用域中,使用 ::
运算符访问:
// LateInitialization/IsInitialized.kt
package lateinitialization
import atomictest.*
class WithLate {
lateinit var x: String
fun status() = "${::x.isInitialized}"
}
lateinit var y: String
fun main() {
trace("${::y.isInitialized}")
y = "Ready"
trace("${::y.isInitialized}")
val withlate = WithLate()
trace(withlate.status())
withlate.x = "Set"
trace(withlate.status())
trace eq "false true false true"
}
虽然可以创建一个局部的 lateinit var
,但你不能在其上调用 .isInitialized
,因为不支持对局部 var
或 val
的引用。
练习和解答可在 www.AtomicKotlin.com 找到。