携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情。
通过前两篇的讨论学习,我们已经对属性的委托理解得很清楚了。今天来看看委托属性都有哪些实际的应用场景吧。
属性委托的背后
在介绍应用场景之前,我们回头再来看看属性委托的背后,探究下它的实现原理。
既然是「委托」,它的核心即是:自己的工作交由别人来处理。这对于属性来讲,就是它的读和写了。类似于类的「委托式继承」,属性的委托,势必也有一个「代理对象」,来完成这个工作。
这是之前文章中的例子:
class Delegate {
operator fun getValue(thisRef: Owner?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Owner?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
class Owner {
var desc: String by Delegate()
}
Owner 的 desc 变量委托给了 Delegate 对象,其对应的 Java 码如下:
public final class Owner {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Owner.class, "desc", "getDesc()Ljava/lang/String;", 0))};
@NotNull
private final Delegate desc$delegate = new Delegate();
@NotNull
public final String getDesc() {
return this.desc$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setDesc(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.desc$delegate.setValue(this, $$delegatedProperties[0], var1);
}
}
看到了吗,Owner 内部生成了一个名为 desc$delegate 的 Delegate 对象,这就是前面所说的「代理对象」了。在读取 desc 值时,调用了 getDesc() 方法,内部则是委托对象 desc$delegate 的 getValue()调用,传入的第一个参数是 this —— 这就是之前文章所说的「持有者」了;委托属性的信息,则由一个数组 KProperty 数组保存。
写值的时候,调用setDesc(),与 get 类似,不再赘述。
所以总的说来,属性的委托,依然靠的是一个特定的「代理」来完成的,只是这个「代理」是自动生成而已。
main() 函数里的委托,拿到的
thisRef为什么是空呢?
应用
委托给另一个属性
这个应用的意思是,一个属性的值的读(写),依赖另一个属性 —— 不好理解?直接看代码就明了了:
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
delegatedToMember 的值的读写,就委托给了 memberInt,意思就是,读写它,实际上读写的是 memberInt。其它变量也是类似的意思。
注意其中 :: 的用法。
map 存值
map 就是「键值对的集合」,将它作为多个变量的委托存储对象,也是委托的一种应用场景。
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
User 类的两个变量 name 和 age,都委托给了参数 map。构造的时候,给到属性值,存储于map中:
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
然后就可以使用了:
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
注意,构造的时候,map 的 key 值,一定就是属性的名字,要不然就报错:
val user = User(mapOf(
"name2" to "John Doe", // 改成name2
"age" to 25
))
Exception in thread "main" java.util.NoSuchElementException: Key name is missing in the map.
at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24)
at User.getName(DelegationAppliction.kt:2)
at DelegationApplictionKt.main(DelegationAppliction.kt:11)
原理
Map的委托特性,不是自带的,而通过「委托扩展」实现的(前面):
public inline operator fun <V, V1 : V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 =
@Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
这也算是之前文章说的委托属性的扩展方法实现的标准库举例了。自然还有 setter:
@kotlin.internal.InlineOnly
public inline operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
this.put(property.name, value)
}
注意这个 setter,有区别,它是 MutableMap 的扩展 —— 毕竟,要写值,就得需要可变的嘛。再来个例:
class User2(map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
访问并改值:
val mutable: MutableMap<String, Any?> = mutableMapOf(
"name" to "John Doe",
"age" to 25
)
val user2 = User2(mutable)
println(user2.name)
println(user2.age)
println(mutable["name"])
println(mutable["age"])
user2.name = "Jane Doe"
user2.age = 30
println(user2.name)
println(user2.age)
println(mutable["name"])
println(mutable["age"])
}
结果:
John Doe
25
John Doe
25
Jane Doe
30
Jane Doe
30
第一次访问,User2 和 传入的 map,保存的值一样;接着,直接通过对象 user2,改变name、age的值,改变成功后,map里的值也变了 —— 因为本来就是委托给它的嘛,值读和写都是它来完成的!
局部变量的委托
局部变量也可以委托,比如懒加载的应用,可以避免不必要的初始化:
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
因为 memoizedFoo 是懒加载委托,所以只有 someCondition 为 true 的时候,才会调用 computeFoo 构造对象;不这么写的化,就显得很麻烦:
fun example(computeFoo: () -> Foo) {
if (someCondition) {
val memoizedFoo = computeFoo()
if (memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
}
小结
好了,时间不早了,委托属性到这里也已经讲得差不多了。下篇再见!