浅谈委托属性 3:委托的背后和应用

通过前两篇的讨论学习,我们已经对属性的委托理解得很清楚了。今天来看看委托属性都有哪些实际的应用场景吧。

属性委托的背后

在介绍应用场景之前,我们回头再来看看属性委托的背后,探究下它的实现原理。

既然是「委托」,它的核心即是:自己的工作交由别人来处理。这对于属性来讲,就是它的读和写了。类似于类的「委托式继承」,属性的委托,势必也有一个「代理对象」,来完成这个工作。

这是之前文章中的例子:

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$delegateDelegate 对象,这就是前面所说的「代理对象」了。在读取 desc 值时,调用了 getDesc() 方法,内部则是委托对象 desc$delegategetValue()调用,传入的第一个参数是 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机器人,可以解答大家在工作上或者是技术上的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值