kotlin的委托分为委托类和委托属性
如果你是Android工程师,就会发现JetpackCompose当中大量使用了Kotlin委托 特性。可以说,如果你不理解委托,你就无法真正理解JetpackCompose。
一、委托类
先从委托类开始,它的使用场景非常简单易懂:它常常用于实现类的"委托模式"。 来看个简单例子:
kotlin
interface DB {
fun save()
}
class SqlDB() : DB {
override fun save() {
println("save to sql")
}
}
class GreenDaoDB() : DB {
override fun save() {
println("save to GreenDao")
}
}
// 参数 通过 by 将接口实现委托给 db
// ↓ ↓
class UniversalDB(db: DB) : DB by db
fun main() {
UniversalDB(SqlDB()).save()
UniversalDB (GreenDaoDB()).save()
}
//输出
//save to sql
//save to GreenDao
上面定义了一个DB接口,它的saveO方法用于数据库存储,SqIDB和GreenDaoDB都实现了这个接口。
接着,UniversalDB也实现了这个接口,同时通过by这个关键字,将接口的实现委托给了它的参数db。
这种委托模式在我们的实际编程中十分常见,UniversalDB相当于一个壳,它虽然实现了DB 这个接口,但并不关心它怎么实现。具体是用SQL还是GreenDao,传不同的委托对象进 去,它就会有不同的行为。
另外,以上委托类的写法,等价于以下Java代码,我们可以再进一步来看下:
java
public class UniversalDB implements DB {
DB db;
public UniversalDB(DB db) {
this.db = db;
}
@Override
public void save() {
// 将save委托给db.save()
db.save();
}
}
上面代码中save() 将执行流程委托给了传入的db对象。
所以 Kotlin的委托类提供了语法层面的委托模式。 通过这个by关键字,就可以自动将接口里的方法委托给一个对象,从 而可以省略很多接口方法适配的模板代码。
此时可能会有这样的疑问,委托这有什么用?
既然都是调用辅助对象的方法来实现,我还不如直接使用辅助对象。 这种模式的意义在于,可以选择性地重写一部分方法的实现,或是在委托的基础上加入新的方法,从而创建功能更加、丰富的"新"数据结构。
重写委托接口的实现
再看个例子
kotlin
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() {
println(x)
}
override fun printMessageLine() {
println(x)
}
}
class Derived(b: Base) : Base by b {
override fun printMessage() {
println("abc")
}
}
fun main(){
val base = BaseImpl(10)
Derived(base).printMessage()
Derived(base).printMessageLine()
}
注意这里Derived(base).printMessage() 会打印abc而不是10
再看一个例子:
kotlin
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x= $x"
override fun print() {
println("BaseImpl print:$message")
}
}
class Derived(b: Base) : Base by b {
override val message = "Message of Derived"
// override fun print() {
// println("Derived print:$message")
// }
}
fun main(){
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
注意这里如果Derived不重写print方法,那么derived.print()实际打印的是: "BaseImpl print:BaseImpl: x= 10"
二、委托属性
委托属性的核心思想是:将一个属性(field)的读写操作委托给另一个类去完成。
看个例子:
kotlin
class Example {
var p: String by Delegate()
}
它代表将 p
属性的 get()
和 set()
方法实现委托给了 Delegate
类。
当访问 p
属性时,会自动调用 Delegate
中的 getValue()
方法;
当给 p
属性赋值时,会自动调用 Delegate
中的 setValue()
方法。
注意这里如果是val p,那么委托的是p的get方法。
看一下Delegate类是什么
kotlin
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
这是标准的代码实现模板:
-
getValue()
方法- 一个参数用于声明该Delegate类的委托功能可以在什么类中使用。例如,在
Example
的实例中访问p
属性时,这个参数就是Example
实例。 - 第二个参数数KProperty<>是Kotlin 中的一个属性操作类,可用于获取各种属性相关的值.用于获取属性的元信息,比如属性名(
prop.name
)。另外,《>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java中<?>的写法。 - 方法的返回值类型需要和委托属性的类型兼容。不是相同,因为返回值类型可以转为委托属性的类型即可。
- 一个参数用于声明该Delegate类的委托功能可以在什么类中使用。例如,在
-
setValue()
方法- 前两个参数和
getValue
相同 - 第三个参数代表要赋给委托属性的新值。注意:这个参数的类型要和 getValue 方法返回值的类型兼容。
- 前两个参数和
不过当委托的属性使用 val 声明时,可以不用实现 setValue
方法,
下面具体看下用法
当你从 p
读取委托给 Delegate
实例时,将调用 Delegate
的 getValue()
函数。它的第一个参数是你从中读取 p
的对象,第二个参数包含 p
本身的描述(例如,可以取其名称)
kotlin
val e = Example()
println(e.p)
这将打印
css
Example@33a17727, thank you for delegating 'p' to me!
同样,当你赋值给 p
时,会调用 setValue()
函数。前两个参数相同,第三个参数保存要分配的值
kotlin
e.p = "NEW"
将打印
css
NEW has been assigned to 'p' in Example@33a17727.
三、标准委托
Kotlin"委托类"委托的是接口方法。
而"委托属性"委托的,则是属性的getter、setter。
我们知道val定义的属性,它只有get()方法;
而var定义的属性,既有get()方法,也有set()方法。
那么,属性的getter、setter委托出去以后,能有什么用呢?
我们可以从Kotlin官方提供的标准委托那里找到答案。
标准委托
Kotlin提供了好几种标准委托,其中包括:
- 两个属性之间的直接委托
- by lazy懒加载委托
- Delegates.observable观察者委托
- by map映射委托。
前面两个的使用频率比较高,后面两个频率比较低。
将属性A委托给属性B
从Kotlin1.4开始,我们可以直接在语法层面将"属性A"委托给"属性B",就像下面这 样:
kotlin
class Item {
var count: Int =0
var total: Int by ::count
}
这里定义了两个变量,count和total, 因为把total这个属性的getter和setter都委托给了count,所以其中total的值与count完全一致,
注意:
by 代表total属性的getter、setter会被委托出去; ::count,代表total被委托给了count。这里的"::count"是属性的引用
total和count两者之间的委托关系一旦建立,就代表了它们两者的getter和setter会完全 绑定在一起,如果要用代码来解释它们背后的逻辑,它们之间的关系会是这样:
kotlin
//近似逻辑,实际上,底层会生成一个ItemStotaLS2类型的delegate来实现
class Item {
var count:Int = 0
var total: Int
get() = count
set(value: Int) {
count = value
}
}
也就是,当total的get()方法被调用时,它会直接返回count的值,也就意味着会调用 count的get()方法;
而当total的set()方法被调用时,它会将value传递给count,也就 意味着会调用count的set()方法。
也许你会好奇:Kotlin1.4提供的这个特性有啥用? 为什么要分别定义count和total? 直接用count不好吗?
这个特性,其实对我们软件版本之间的兼容很有帮助。假设Item是服务端接口的返回数据, 1.0版本的时候,我们的Item当中只count这一个变量:
kotlin
class Item {
var count:Int = 0
}
而到了2.0版本的时候,我们需要将count修改成total。
这时候问题就出现了,如果我们直接将count修改成total,我们的老用户就无法正常使用了。
但如果我们借助委托,就可以很方便地实现这种兼容。 我们可以定义一个新的变量total,然后将其委托给count。
这样的话,2.0的用户访问total,而1.0的用户访问原来的count,由于它们是委托关系,也不必 担心数值不一致的问题。
懒加载委托
懒加载,就是对于一些需要消耗计算机资源的操作,希望它在被访问的时候才 去触发,从而避免不必要的资源开销。其实,这也是软件设计里十分常见的模式。
看个例子:
kotlin
val data: String by lazy{
request()
}
fun request(): String {
println("执行网络请求")
return "返回数据"
}
fun main() {
println("开始")
println(data)
println(data)
}
//开始
//执行网络请求
//返回数据
//返回数据
通过"by lazy{}",就可以实现属性的懒加载了。
这样,上面的执行结果我们会发现: main函数的第一行代码,由于没有用到data,所以request函数也不会被调用。
第二行代码,我们要用到data的时候,request才会被触发执行。
到了第三行代码,由于前面我们已经知道了data的值,因此也不必重复计算,直接返回结果即可。 所以日志中看到"执行网络请求"只打印了1次
这里lazy其实是个高阶函数
,并不是什么关键字

kotlin源码
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
lazy()
是一个接受 lambda 并返回 Lazy<T>
实例的函数,它可以作为实现惰性属性的委托。
第一次调用 get()
会执行传递给 lazy()
的 lambda 并记住结果。后续对 get()
的调用只是返回记住的结果。
可以看到,lazy函数还可以接收一个LazyThreadSafetyMode类型的参数,如果我们不传这个参数,它就会直接使用SynchronizedLazylmpl的方式。
而从LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) 这句可以看出来,它是为了多线程同步的。而剩下的SafePublicationLazylmpl、UnsafeLazylmpl,则不是多线程安全的。
默认情况下不传LazyThreadSafetyMode,延迟属性的计算是同步的 :该值仅在一个线程中计算,但所有线程都将看到相同的值。
如果不需要多线程同步可以将 LazyThreadSafetyMode.PUBLICATION
作为参数传递给 lazy()
如果确定初始化将始终与使用该属性的线程在同一线程中进行,则可以使用 LazyThreadSafetyMode.NONE
。它不会产生任何线程安全保证和相关开销。
实现简化的 lazy 函数
了解了委托属性后,可以实现一个自己的 lazy
(懒加载)函数。它会将属性的初始化代码延迟到它第一次被访问时,才被执行。
下面来实现一个自己的懒加载函数 later
。
首先,实现符合委托规范的 Later
类。代码如下
kotlin
class Later<T>(private val block: () -> T) {
private var value: Any? = null
operator fun getValue(thisRef: Any?, prop: KProperty<*>): T {
if (value == null) {
println("inside later block (initializing...)")
value = block()
}
@Suppress("UNCHECKED_CAST")
return value as T
}
}
然后,创建一个顶层 later
函数,用于返回 Later
类的实例,代码如下:
kotlin
fun <T> later(block: () -> T) = Later(block)
测试一下:
kotlin
fun main() {
val p by later {
println("inside later block")
"this a later string"
}
println("before access p")
println("p is $p")
println("after access p")
}
可以看到 Lambda 表达式中的初始化代码,只有在第一次访问 p
属性时才会执行。这就是实现了懒加载的效果。
最后注意:这只是大致实现了 lazy
函数的功能。在真实项目中,还是要使用标准库中的 lazy
函数。因为它解决了线程安全的问题,防止在多线程的环境下,导致初始化操作被重复执行;解决了空值处理的问题,如果 block
块执行的结果是空(value = null
),那么下次访问委托的属性而调用 getValue
方法时,还是会进入 value == null
的判断,导致重复执行 block
块中的代码。
自定义委托
kotlin
import kotlin.reflect.KProperty
class StringDelegate(private var s: String = "hello") {
operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
return s
}
operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
s = value
}
}
class Owner {
var text: String by StringDelegate()
}
首先,对于var修饰的属性,我们必须要有getValue、 setValue这两个方法,同时,这两个方法必须有operator关键字修饰。
其次,我们的text属性是处于Owner这个类当中的,因此 getValue、setValue这两个方法中的thisRef的类型,必须要是Owner,或者是Owner的 父类。也就是说,我们将thisRef的类型改为 Any 也是可以的。
当我们不确定委托属性会处于哪个类的时候,就可以将thisRef的类型定义为 "Any?" 。
最后,由于我们的text属性是String类型的,为了实现对它的 委托,getValue的返回值类型,以及setValue的参数类型,都必须是String类型或者是它 的父类。
而如果你觉得这样的写法实在很繁琐,也可以借助Kotlin提供的ReadWriteProperty、 ReadOnlyProperty这两个接口,来自定义委托。
如果我们需要为val属性定义委托,我们就去实现ReadOnlyProperty这个接口;如果我们 需要为var属性定义委托,我们就去实现ReadWriteProperty这个接口。这样做的好处是, 通过实现接口的方式,Intellij可以帮我们自动生成override的getValue、setValue方法。
以前面的代码为例,我们的 StringDelegate,也可以通过实现ReadWriteProperty接口来编 写:
kotlin
class StringDelegate(private var s: String = "hello"): ReadWriteProperty<Owner, String> {
override operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
return s
}
override operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
s = value
}
}
Delegates.observable观察者委托
Delegates.observable() 接受两个参数:初始值和修改处理程序
每次分配给属性时(执行赋值后 )都会调用处理程序。 它有三个参数:要赋值的属性、旧值和新值:
kotlin
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
// 执行结果:
//<no name> -> first
//first -> second
vetoable 是 Kotlin 标准库给 可变属性(var) 提供的一个 "拦截器" ------
在 真正赋值之前 先跑一段你自己的逻辑,
如果你返回 false ,这次赋值就被 否决(veto) ,属性值保持原样;
返回 true 才正式生效。
一句话: "想改可以,先过我这关。"
函数签名
kotlin
public inline fun <T> vetoable(
initialValue: T, // 初始值
crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean
): ReadWriteProperty<Any?, T>
onChange
返回 Boolean:
- true → 接受新值
- false → 拒绝新值,属性不变
kotlin
var percentage: Int by Delegates.vetoable(0) { _, old, new ->
// 只允许 0..100 之间的数
new in 0..100
}
fun main() {
percentage = 50 // 成功
println(percentage) // 50
percentage = 200 // 被否决
println(percentage) // 仍是 50
}
典型使用场景
- 范围校验 / 业务规则
kotlin
var price: BigDecimal by Delegates.vetoable(BigDecimal.ZERO) { _, _, new ->
new >= BigDecimal.ZERO // 拒绝负价
}
- 防抖动(拒绝相同值)
kotlin
var selectedId: Long by Delegates.vetoable(-1) { _, old, new ->
new != old // 相同 ID 直接无视
}
- 状态机驱动:拒绝非法转移
sql
enum class State { IDLE, RUNNING, PAUSED, STOPPED }
var state: State by Delegates.vetoable(State.IDLE) { _, old, new ->
// 只允许合法转移
when {
old == State.IDLE && new == State.RUNNING -> true
old == State.RUNNING && new == State.PAUSED -> true
old == State.PAUSED && new == State.RUNNING -> true
old == State.RUNNING && new == State.STOPPED -> true
else -> false
}
}
- Android 实战:View 宽高只能增大
javascript
var w: Int by Delegates.vetoable(0) { _, old, new -> new >= old }
var h: Int by Delegates.vetoable(0) { _, old, new -> new >= old }
by map映射委托
一种常见的用例是将属性值存储在map中。这经常出现在解析 JSON 或执行其他动态任务等应用程序中。在这种情况下,可以使用映射实例本身作为委托属性的委托。
kotlin
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
在此示例中,构造函数采用一个map:
sql
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
委托属性通过字符串键从此映射中获取值,这些键与属性名称相关联:
scss
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
如果使用 MutableMap
而不是只读 Map
,这也适用于 var
的属性
kotlin
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
val user = MutableUser(mutableMapOf(
"name" to "John Doe",
"age" to 25
))
fun main() {
println(user.name)
println(user.age)
}
本地委托属性
可以将局部变量声明为委托属性。例如,您可以使局部变量惰性:
kotlin
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo
变量将仅在首次访问时计算。如果 someCondition
失败,则根本不会计算变量。
委托属性的转换规则
在后台,Kotlin 编译器会为某些类型的委托属性生成辅助属性,然后委托给它们。 出于优化目的,编译器在某些情况下不会生成辅助属性。
例如,对于属性prop
,它生成隐藏属性 prop$delegate
,而访问器的代码只是委托给这个附加属性:
kotlin
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
otlin 编译器在参数中提供了有关 prop
的所有必要信息:第一个参数 this
引用了外部类 C
的实例,this::prop
是描述 prop
本身的 KProperty
类型的反射对象。
委托属性的优化案例
如果委托是以下情况,则将省略 $delegate
字段:
- 引用的属性:
csharp
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
- 命名对象:
kotlin
object NamedObject {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}
val s: String by NamedObject
-
在同一模块中具有支持字段和默认 getter 的最终
val
属性 优化触发条件(四条必须全部满足) 4. val (不能是 var) 5. final (不能是 open / override) 6. 有幕后字段 (也就是不是get() = ...
这种纯自定义 getter) -
默认 getter (你没有手写
get() = ...
) -
代理实例是编译期已知的常量 (最常见的就是同一模块 里的顶层
val impl = ...
)
kotlin
// file: Util.kt
val impl = ReadOnlyProperty<Any?, String> { _, _ -> "hello" }
// file: A.kt
class A {
val s: String by impl
}
编译器不再生成 $$delegate
,而是直接把 getter 内联成:
kotlin
public final class A {
// <== 没有 $$delegate_0 字段
@NotNull
public final String getS() {
return impl.getValue(this, $$s$metadata); // 直接引用顶层单例 impl
}
}
在 Kotlin 里,val
默认就是 final
,你不需要、也不能再显式写 final
只有当属性是 var
或者显式写成 open val
/ override val
时,才会失去"final"这一优化前提。
- constant expression常量表达式, enum 枚举,this,null。 this的例子:
kotlin
class A {
operator fun getValue(thisRef: Any?, property: KProperty<*>) ...
val s by this
}
委托给其他属性时的转换规则
委托给另一个属性时,Kotlin 编译器会立即生成对引用属性的访问权限。这意味着编译器不会生成字段 prop$delegate
。这种优化有助于节省内存。
以以下代码为例:
csharp
class C<Type> {
private var impl: Type = ...
var prop: Type by ::impl
}
prop
变量的属性访问器直接调用 impl
变量,跳过委托属性的 getValue
和 setValue
运算符,因此不需要 KProperty
引用对象。
对于上面的代码,编译器会生成以下代码:
kotlin
class C<Type> {
private var impl: Type = ...
var prop: Type
get() = impl
set(value) {
impl = value
}
fun getProp$delegate(): Type = impl // 这个方法只为反射使用
}
提供委托(provideDelegate)
实际上,要想在属性委托之前再做一些额外的判断工作,我们可以使用provideDelegate来 实现。
看看下面的SmartDelegator你就会明白:
kotlin
class SmartDelegator {
operator fun provideDelegate(
thisRef: Owner,
prop: KProperty<*>
): ReadWriteProperty<Owner, String> {
return if (prop.name.contains("log")) {
return StringDelegate("log")
} else {
return StringDelegate("normal")
}
}
}
class Owner{
var normalText: String by SmartDelegator()
var logText: String by SmartDelegator()
}
fun main() {
val owner = Owner()
println(owner.normalText)
println(owner.logText)
}
为了在委托属性的同时进行一些额外的逻辑判断,我们使用创建了一个新的 SmartDelegator,通过它的成员方法provideDelegate 嵌套了一层,在这个方法当中,我们 进行了一些逻辑判断,然后再把属性委托给StringDelegate。
这样,通过provideDelegate ,不仅可以嵌套Delegator,还可以根据 不同的逻辑派发不同的Delegator。
实战与思考
案例1:属性可见性封装
在软件设计当中,我们会遇到这样的需求:对于某个成员变量data,我们希望类的外部可以 访问它的值,但不允许类的外部修改它的值。因此我们经常会写出类似这样的代码:
kotlin
class Model {
var data: String = ""
// 我们将data属性的set方法声明为private的,
// 这时候,data属性的set方法只能从类的内部访问,
// 这就意味着类的外部无法修改data的值了,
// 但类的外部仍然可以访问data的值
private set
private fun load(){
// 网络请求
data = "请求结果"
}
}
这样的代码模式很常见,我们在Java/C当中也经常使用,不过当我们的data类型从String 变成集合以后,问题就不一样了。
kotlin
class Model {
val data: MutableList<String> = mutableListOf()
private fun load(){
// 网络请求
data.add("hello")
}
}
fun main() {
val model = Model()
// 类的外部仍然可以修改data
model.data.add("World")
}
对于集合而言,即使我们将其定义为只读变量val,类的外部一旦获取到data的实例,它仍 然可以调用集合的addO方法修改它的值。这个问题在Java当中几乎没有优雅的解法。只要 你暴露了集合的实例给外部,外部就可以随意修改集合的值。这往往也是Bug的来源,这样 的Bug 还非常难排查。
而在这个场景下,我们前面学习的"两个属性之间的委托"这个语法,就可以派上用场了。
kotlin
class Model {
val data: List<String> by:: _data
private val _data: MutableList<String> = mutableListOf()
fun load() {
_data.add("Hello")
}
}
fun main() {
val model = Model()
// 编译报错
model.data.add("World")
}
在上面的代码中,我们定义了两个变量,一个变量是公开的"data",它的类型是List,这是 Kotlin当中不可修改的List,它是没有add、remove等方法的。
接着,我们通过委托语法,将data的getter委托给了_data这个属性。而_data这个属性 的类型是MutableList,这是Kotlin当中的可变集合,它是有add、remove方法的。由于 它是private修饰的,类的外部无法直接访问,通过这种方式,我们就成功地将修改权保留在 了类的内部,而类的外部访问是不可变的List,因此类的外部只能访问数据。
案例2:数据与View的绑定
在Android当中,如果我们要对"数据"与"View"进行绑定,我们可以用DataBinding, 不过DataBinding太重了,也会影响编译速度。其实,除了DataBinding以外,我们还可以 借助Kotlin的自定义委托属性来实现类似的功能。这种方式不一定完美,但也是一个有趣的 思路。
这里我们以TextView为例:
kotlin
operator fun TextView.provideDelegate(value: Any?, property: KProperty<*>) = object : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? = text
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
text = value
}
}
以上的代码,为TextView定义了一个扩展函数TextView.provideDelegate,
而这个扩展函数的返回值类型是ReadWriteProperty。
通过这样的方式,我们的TextView就相当于支持了String属性的委托了。
它的使用方式也很简单:
arduino
val textView = findViewById<textView>(R.id.textView)
// ①
var message: String? by textView
// ②
textView.text = "Hello"
println(message)
// ③
message = "World"
println(textView.text)
结果:
Hello
World
在注释①处的代码,我们通过委托的方式,将message委托给了textView。这意味着,message的getter和setter都将与TextView关联到一起。
在注释②处,我们修改了textView的text属性,由于我们的message也委托给了textView,因此这时候,println(message)的结果也会变成"Hello"。
在注释③处,我们改为修改message的值,由于message的setter也委托给了textView,因此这时候,println(textView.text)的结果会跟着变成"World"。
案例3:ViewModel委托
在Android当中 ViewModel 应该可以算是Jetpack 中最重要的组件之一了
我们会经常用到ViewModel来存储界面数据。同时,我们不会直接创建ViewModel的实例,而对应的,我们会使用委托的方式来实现。
csharp
// MainActivity.kt
private val mainViewModel: MainViewModel by viewModels()
这一行代码虽然看起来很简单,但它背后隐藏了ViewModel复杂的实现原理。
我们先来看看viewModels()是如何实现的:
kotlin
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
原来,viewModels()是Activity的一个扩展函数。
也是因为这个原因,我们才可以直接在Activity当中直接调用viewModels()这个方法。
另外,viewModels()这个方法的返回值类型是Lazy,那么,它是如何实现委托功能的呢?
Lazy类在外部还定义了一个扩展函数getValue(),这样,只读属性的委托就实现了。
kotlin
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value