类委托
类委托实现原理简单,就是普通的代理模式。
- 实现一个统一的接口
- 接收一个真实的接口实现对象为参数
- 通过关键字
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 field
private 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个接口
// 只读接口,对应 val
public fun interface ReadOnlyProperty<in T, out V> {
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
// 读写接口,继承了只读接口,对应 var
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
实现接口的被委托类
class FirstDelegate : ReadWriteProperty<Any?, Int>{
private var invokeGetAmount = 0
private var invokeSetAmount = 0
override 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 = 22
println(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 = 0
private 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.InlineOnly
public 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 instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
实现比较简单,就是通过双重判空的锁实现,与单例模式一样
总结
委托属性的作用,一般都是与泛型+高级函数
一起使用,这样才能把同一类型的操作简化为一个by关键字+委托类
实现
自己需要用到属性委托的地方都是为了解决一类问题,而不是单个问题,遇到的时候再补充