携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情。
上一篇文章,我们讨论了 Kotlin 的委托以及它在继承中的应用,今天,来继续讨论下另一种委托:委托属性 —— 相当于从类的维度,缩小到了属性的维度。
概念
「委托属性」,从名字上,我们大概就能理解到它到底要干什么:第一,既然是「委托」,那免不了需要将相关功能委托给别人来帮忙完成;第二,委托的是属性,属性能委托什么呢?那自然就是其值的读写了。只要有这两点认识,我们就基本清楚了委托属性存在的意义。
简单说,「委托属性」就是将属性的读写委托给一个代理,由这个代理决定该属性的读写值,乃至读写行为。委托的发生,同样由关键词 by 触发,其句法为:
val/var <property name>: <Type> by <expression>
其中:
- property name:属性名
- Type:属性类型
- expression:属性的委托表达式
属性的 get() 和 set() 会被分别委托给两个方法: getValue() 和 setValue(),这俩方法由 expression 提供。比如:
import kotlin.reflect.KProperty
class Delegate {
// String类型的委托get
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
// String类型的委托set
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
来看看效果:
var desc: String by Delegate()
fun main(args: Array<String>) {
desc = "test"
val r = desc
println(r)
}
输出:
test has been assigned to 'desc' in null.
null, thank you for delegating 'desc' to me!
可变变量 desc 使用了 Delegate 委托。对 desc 赋值时,调用 setValue() 方法打印了相应的日志,value 是新值,thisRef 是 desc 引用。
将 desc 赋值给 r 时,会调用 getValue() 方法获取值,可以看到,这里还是 null,因为根本没有地方存储值,set 没有意义,值也无法保存。
值得注意的是,getValue() 和 setValue() 前面都加了 operator,这说明,属性委托根本就是利用了赋值和读值的操作符重载技术!
对于 val 类型的属性,只需要
getValue()即可。
标准库委托
标准库默认提供了一些十分实用的委托工具,其中就包括我们经常看到的「懒加载」委托 lazy。
lazy
lazy 就是为「懒加载」设计的,它有两个要点:
- 使用时初始化 —— 懒加载
- 每次访问,使用初始化完成的已有值
怎么做的呢?
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
首先,lazy 其实就是一个工厂方法,返回一个 Lazy<T> 类型,而该类型对象由其实现类 SynchronizedLazyImpl 构造。来看看源码:
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 is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
// val类型变量,get()其值
get() {
val _v1 = _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 {
// 未初始化,调用初始化工厂函数,获取T值
val typedValue = 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)
}
public interface Lazy<out T> {
/**
* 懒加载实例,一次性构造
*/
public val value: T
/**
* 标志是否初始化完成
*/
public fun isInitialized(): Boolean
}
从上述代码可以看出,lazy 只能委托给 val 类型的变量,因为它只提供了 get()。也就是在 get()的逻辑中,它实现了「懒加载」和「一次性初始化」的功能。
lazy还有非同步版本,这里不作深入探讨
Delegates
Delegates 是一个 object,它提供以下标准属性委托:
notNull:非空委托,可以让非空属性避免声明初始化observable:可观察委托,每次赋值完成后,回调告知新旧值变化vetoable:可拒绝委托,每次赋值生效前,判断一下条件是否满足
其中,可观察、可拒绝的委托,都是通过 ObservableProperty (可观察属性)来实现的。
public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
private var value = initialValue
/**
赋值前回调。其返回值,false:丢弃,true:继续赋值
*/
protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true
/**
赋值完成后回调
*/
protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}
public override fun getValue(thisRef: Any?, property: KProperty<*>): V {
return value
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
val oldValue = this.value
// 这里先回调before,如果返回false,就return丢弃了,值不更新
if (!beforeChange(property, oldValue, value)) {
return
}
// 走到这里,证明可以更新,更新并回调after
this.value = value
afterChange(property, oldValue, value)
}
}
源码很简单。而 observable 和 vetoable 就分别覆写了 afterChange 和 beforeChange 来达到「观察」、「拒绝」的效果:
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
// 通过 onChange() -> Unit 传递回调
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
// 通过onChange() -> Boolean 传递回调
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}
其中,vetoable 的 onChange 是需要返回布尔值的,用于判断待赋的值,抛弃与否。
小结
今天算是对委托属性有了一个基本认识,也学习了下标准库里常用的委托属性工具的使用及其原理。一句话总结,委托属性就是为属性的读写服务的。