通过前两篇的讨论学习,我们已经对属性的委托理解得很清楚了。今天来看看委托属性都有哪些实际的应用场景吧。
属性委托的背后
在介绍应用场景之前,我们回头再来看看属性委托的背后,探究下它的实现原理。
既然是「委托」,它的核心即是:自己的工作交由别人来处理。这对于属性来讲,就是它的读和写了。类似于类的「委托式继承」,属性的委托,势必也有一个「代理对象」,来完成这个工作。
这是之前文章中的例子:
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()
}
}
}
小结
好了,委托属性到这里也已经讲得差不多了。下篇再见!
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题