关键字 by 在 kotlin 主要用于委托实现。Kotlin 编译器会额外生成一个类用来处理委托,这就是委托实现的底层逻辑。
类委托
- 如果被委托类实现多个接口,只有
每一个接口都需要使用 by 关键字定义委托实现。- 如果不同的接口有相同方法,
当前类必须自己实现该方法- 被委托类
未实现的方法直接会代理给委托类,已实现的方法不会变化。- 其原理是:kotlin 在编译生成 class 文件时会自动将未实现的方法代理给委托类中同一方法。
如下:
// 多接口委托
class Test: Itf1 by Itf1Impl(),Itf2 by Itf2Impl(){
override fun itf() {
}
}
// 单接口委托
class Test(itf:Itf):Itf2,Itf by itf{
override fun test2() { // 定义在 Itf 接口中
// test() 方法 Test 类自己实现了,不需要委托给别的对象
Log.e(TAG, "test: self")
}
}
通过反编译相应的 class 文件,可发现 构造函数中使用成员变量记录委托类,并在相应的方法中调用委托类对应的方法。首先是构造函数
下面是 test() 方法,整体没任何多余逻辑,就是将请求转给 $$delegate_0 字段
属性委托
- 将一个属性的
getter/setter委托给另一个类的getValue/setValue方法。- 其原理是:使用成员变量记录委托类,在生成的
getter/setter中自动调用getValue/setValue- 当前类
不会存储属性值,因此委托类可以根据需要决定存储或不存储属性值。可以用来实现对属性值的过滤
如下,使用 i 属性时就会调用到下面的 getValue/setValue
class Test{
// 将 i 委托给 IDelegate 对象
// 每一次对 i 的 getter/setter 都会调用 IDelegate 的 getValue 与 setValue 方法
var i: Int by IDelegate()
}
// 新版本的 studio 会自动生成 getValue/setValue 方法,不需要记具体写法
class IDelegate{
// delegate 参数必须是 Test 的父类
operator fun getValue(delegate: Any, property: KProperty<*>): Int {
return 1
}
operator fun setValue(delegate: Any, property: KProperty<*>, i: Int) {
}
}
同样,反编译对应的 class 文件可以看出:
- 构造函数中会生成一个
IDelegate类型的成员变量, 名为i$delegate - 在
i的getter/setter中会调用i$delegate的getValue/setValue方法
以生成的 setter 为例
lazy
其原理是懒汉式单例,构造函数中首先会将 lambda 表达式封装在一个 Lazy 对象中。在属性的 getter 方法中会调用 Lazy 对象的 getValue 方法,getValue 会使用懒汉式写法保存属性值
可观察属性 Delegates.observable
当为属性赋值时,会回调第二个参数 lambda 表达式
lambda 表达式会被转换成一个类,该类继承自 ObservableProperty(它又实现了 ReadWriteProperty 接口),同时将 lambda 表达式的内容填充到 afterChange 方法中
属性的 getter/setter 都被代理给 ReadWriteProperty 的 getValue/setValue。所以当为属性赋值时就是调用 ObservableProperty 的 getValue 方法,该方法又会调用 afterChange 方法,也就是我们的 lambda 表达式。
private var i: Int by Delegates.observable(-1){_,o,n->
Log.e(TAG, "o = : $o, n = $n" )
}
Delegates.vetoable
与上面的 observable 一样,只不过 lambda 表达式会被封装到 beforeChange 中,可以检测赋值给属性的值是否合理。如果返回为 true 该值就是合理的,否则不合理。
具体逻辑在 ReadWriteProperty 的 setValue 方法中。