类委托
类委托实现原理简单,就是普通的代理模式。
- 实现一个统一的接口
- 接收一个真实的接口实现对象为参数
- 通过关键字
by来省略接口方法的覆写
一个简单的例子
接口
interface Defence {/*** 防御攻击* @param attackAmount 攻击次数*/fun defenceAttack(attackAmount: Int)}
实现类
class BaseDefence : Defence {val declaration = "最好的攻击就是防御!!"fun showDeclaration() {println(declaration)}override fun defenceAttack(attackAmount: Int) {println("${this.javaClass.simpleName} 我挡住了${attackAmount}次攻击")}}
委托类
class MyDefence(impl: Defence) : Defence by impl {}
调用
fun main() {val baseDefence = BaseDefence()val myDefence = MyDefence(baseDefence)myDefence.defenceAttack(22)}
打印结果:
BaseDefence 我挡住了22次攻击
匿名类委托
// 匿名类,也可以使用类委托val d = object : Defence by baseDefence {}d.defenceAttack(2)
BaseDefence 我挡住了2次攻击
实现原理
实现原理很简单,反编译MyDefence类即可以明白
public final class MyDefence implements Defence {// $FF: synthetic fieldprivate final Defence $$delegate_0;public void showDeclaration() {String var1 = "我的防御99999";boolean var2 = false;System.out.println(var1);}public MyDefence(@NotNull Defence impl) {Intrinsics.checkNotNullParameter(impl, "impl");super();this.$$delegate_0 = impl;}public void defenceAttack(int attackAmount) {this.$$delegate_0.defenceAttack(attackAmount);}}
结论:
- 与代理模式一模一样,都是通过构造函数传入具体实现对象
- 自动生成代理接口中的所有方法,并且由构造函数传入的实现类调用
- 方法可以被覆盖,覆盖的方法由本类调用
委托属性
这是kotlin中非常关键的一个特征,by lazy{}等都是通过此来实现的
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
基本用法
被委托的类需要实现2个接口
// 只读接口,对应 valpublic fun interface ReadOnlyProperty<in T, out V> {public operator fun getValue(thisRef: T, property: KProperty<*>): V}
// 读写接口,继承了只读接口,对应 varpublic interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {public override operator fun getValue(thisRef: T, property: KProperty<*>): Vpublic operator fun setValue(thisRef: T, property: KProperty<*>, value: V)}
实现接口的被委托类
class FirstDelegate : ReadWriteProperty<Any?, Int>{private var invokeGetAmount = 0private var invokeSetAmount = 0override fun getValue(thisRef: Any?, property: KProperty<*>): Int {invokeGetAmount++println("对象:'${thisRef?.javaClass?.simpleName}'中的'${property.name}'获取了一次值,返回数值:${invokeGetAmount}")return invokeGetAmount}override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {invokeSetAmount++println("对象:'${thisRef?.javaClass?.simpleName}'中的'${property.name}'赋值一次${value},调用次数${invokeSetAmount}")}}
赋值委托
// 使用委托属性var d1 : Int by FirstDelegate()// 根据被委托的类getValue、setValue自动推断var d2 by FirstDelegate()// 自动推断类型 与 当前定义的String类型不符合,报错//var d3 : String by FirstDelegate()fun main() {d1 = 22println(d1)println(d1)}
对象:’null’中的’d1’赋值一次22,调用次数1 对象:’null’中的’d1’获取了一次值,返回数值:1 1 对象:’null’中的’d1’获取了一次值,返回数值:2 2
结论:
- 委托属性使用关键字
by,并且不用实现接口 - 被委托类需要实现接口
ReadOnlyProperty或者ReadWriteProperty - 委托泛型
T,委托属性当前引用所在的空间,即thisRef,如果空间不符合被委托类的定义范围,则不能委托(比如顶层属性所在的空间为null,如果thisRef类型为Any,则不能接顶层属性的委托,必须为Any?才行) - 委托泛型
V,get()、set()方法的类型,必须保持一致。委托属性必须是该类型V才能委托给该类
operator关键字高级写法
class SecondDelegate {private var getValueAmount = 0private var setValueAmount = 0// operator 关键字就可以告诉编译器这是覆写了某个操作,不再需要继承接口operator fun getValue(thisRef: Any?, property: KProperty<*>): String {val str = "第${++getValueAmount}次读取该属性数值"println("对象:${thisRef?.javaClass?.simpleName} 中的 ${property.name} 获取了一次值,返回值:${str}")return str}operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {val str = "第${++setValueAmount}次赋值->$value"println("对象:${thisRef?.javaClass?.simpleName} 中的 ${property.name} 赋值一次:${str}")}}
作用
单独看委托属性,就是把某个属性的get(),set()方法单独扔出去到外部实现,作用并不是太大。
但是结合泛型+高级函数,就可以实现一类型的操作封装,也正是委托属性的关键意义
by lazy{} 分析
val str by lazy { "abc" }
by lazy{} 可以实现通用的延迟初始化操作,类似懒汉式的单例,并且不需要自己写代码实现,非常实用,分析其实现过程就可以了解属性委托的作用
源码:
public actual fun <T> lazy(initializer: () -> T): Lazy<T>= SynchronizedLazyImpl(initializer)
lazy方法接收一个高级函数,initializer: () -> T� 返回值Lazy<T>,具体实现过程先不管,看看返回值是什么
public interface Lazy<out T> {/*** Gets the lazily initialized value of the current Lazy instance.* Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.*/public val value: T/*** Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.* Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.*/public fun isInitialized(): Boolean}
这是一个接口,没有getValue()方法,那怎么能被委托呢??,继续往下找
@kotlin.internal.InlineOnlypublic inline operator fun <T> Lazy<T>.getValue(thisRef: Any?,property: KProperty<*>): T = value
委托函数getValue()是通过扩展函数实现的,这种方式也能够实现委托!
返回值 : T = value,直接返回了接口中的 public val value: T,
那么只需要查看接口的实现类SynchronizedLazyImpl(initializer)中value赋值的地方即可
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {private var initializer: (() -> T)? = initializer@Volatile private var _value: Any? = UNINITIALIZED_VALUE// final field is required to enable safe publication of constructed instanceprivate val lock = lock ?: thisoverride val value: Tget() {val _v1 = _valueif (_v1 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")return _v1 as T}return synchronized(lock) {val _v2 = _valueif (_v2 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)} else {val typedValue = initializer!!()_value = typedValueinitializer = nulltypedValue}}}override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUEoverride fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."private fun writeReplace(): Any = InitializedLazyImpl(value)}
实现比较简单,就是通过双重判空的锁实现,与单例模式一样
总结
委托属性的作用,一般都是与泛型+高级函数一起使用,这样才能把同一类型的操作简化为一个by关键字+委托类实现
自己需要用到属性委托的地方都是为了解决一类问题,而不是单个问题,遇到的时候再补充
