Kotlin 的委托机制在语言层面自动实现了 Java 的组合代理。Kotlin 的委托包括委托类、委托属性,使用 by 关键字表示委托。
委托类
假设有一个接口类 Db,用来保存数据。
interface Db {
fun save()
}
Db 有两个具体的实现类 SqlDb 和 GreenDaoDb
class SqlDb : Db {
override fun save() {
println("save sql db")
}
}
class GreenDaoDb : Db {
override fun save() {
println("save green dao db")
}
}
如果使用 Java 风格的委托方式,会这么写:
class MyDb(private val db: Db) : Db {
override fun save() {
db.save()
}
}
这种方式有很多样板代码,比如重写 save(),在接口方法调用属性 db 的 save()。 ?
Kotlin 的 委托机制使用 by 关键字,自动帮我们省去了样板代码。 ?
只需要使用 by 关键字表明委托给了谁。
class UniversalDb(db: Db) : Db by db
Db 接口的所有方法都交给 by 关键字后面的 db 实现。 ?
它等价于:
class UniversalDb(private val db: Db) : Db {
override fun save() {
db.save()
}
}
?
委托类的使用如下:
fun main() {
println("\ndelegate class")
UniversalDb(SqlDb()).save()
UniversalDb(GreenDaoDb()).save()
}
将任意一种 Db 接口的实现类作为委托类 UniversalDb 的入参,UniversalDb 的 save 方法实际是由入参实现的。 ?
总结:委托类帮我们自动实现了接口的方法。 ?
?
?
委托属性
如果说委托类实现了接口方法,那么委托属性实现了属性的 getter/setter 方法。 ?
Item 类将它的 total 属性委托给了 count 属性。
class Item {
var count: Int = 0
var total: Int by ::count
}
注意委托属性的 by 关键字后面是一个双冒号 :: ,表示属性引用操作符。 ?
假如不写 by ::count,而是写 by count,编译器会报错提示:
class Item {
var count: Int = 0
var total: Int by count
}
Type 'Int' has no method 'getValue(Item, KProperty<*>)' and thus it cannot serve as a delegate
Type 'Int' has no method 'setValue(Item, KProperty<*>, Int)' and thus it cannot serve as a delegate for var (read-write property)
编译器提示 Int 类型没有实现 getValue 和 setValue 方法,不能作为委托属性。 ?
而 ::count 属性引用是 KMutableProperty0 类,它帮我们实现了 get 和 set。
item 的 count 值改变时,委托属性 total 也跟着改变。
fun main() {
println("\ndelegate property")
val item = Item()
item.count = 2
println("total:${item.total}")
}
?
懒加载委托
kotlin 提供了 lazy 方法实现懒加载委托,也就是只在 data 第一次被使用的时候才开始加载。
val data: String by lazy {
request()
}
fun request(): String {
println("执行网络请求")
return "网络数据"
}
?
第一次 println(data) 会打印 “执行网络请求”,而第二次不会。因为使用 lazy 方法将 request 的结果保存,第二次打印 data 时,直接返回 reqeust 结果,而不是再次执行 request。
fun main() {
println("\nlazy delegate")
println("开始")
println(data)
println(data)
}
lazy delegate
开始
执行网络请求
网络数据
网络数据
kotlin 的 lazy 方法定义在 LazyJVM.kt,返回一个 SynchronizedLazyImpl 类的示例。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
SynchronizedLazyImpl 实现了一个单例模式,如果 _value 初始化过,直接返回 _value 的值,否则使用 lazy 的入参函数 initializer 初始化,返回 T 类型的值,并赋值给 _value。
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
...
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
...
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
}
}
}
}
自定义委托
除了标准的委托属性,kotlin 还提供了自定义委托的方式实现委托属性。 ?
以 Owner 为例,它的 text 属性委托给了 StringDelegate,textReadWrite 属性委托给了 StringReadWritePropertyDelegate。这两个都是自定义委托。 ?
需要注意的是两者都使用了括号 () 操作符,因为委托需要指定一个对象示例。
class Owner {
var text: String by StringDelegate()
var textReadWrite: String by StringReadWritePropertyDelegate()
}
StringDelegate 实现了 getValue 和 setValue 方法,用来实现被委托属性的 getter 和 setter。 getValue 和 setValue 方法的入参、返回值等签名信息必须严格按照格式来写,否则编译不过。 ?
thisRef 表示属性所在的类,property 表示被委托的属性,value 表示 setter 需要设置给属性的值。
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
}
}
?
为了简化自定义委托的实现,kotlin 提供了一些标准属性接口。 ?
ReadWriteProperty
?
ReadWriteProperty 可读写属性接口,它的签名和上面自己写的 StringDelegate 是一样的。
class StringReadWritePropertyDelegate(private var s: String = "Hello custom") :
ReadWriteProperty<Owner, String> {
override fun getValue(thisRef: Owner, property: KProperty<*>): String = s
override fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
s = value
}
}
?
ReadOnlyProperty
?
如果是只读属性,可以实现 ReadOnlyProperty。
public fun interface ReadOnlyProperty<in T, out V> {
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
?
使用自定义委托:
println("\ncustom property delegate")
val owner = Owner()
println(owner.text)
println(owner.textReadWrite)
输出如下:
custom property delegate
Hello
Hello custom
PropertyDelegateProvider
除了上述可读写、只读的委托属性,Kotlin 还提供了委托属性提供者 PropertyDelegateProvider,用来提供委托属性。 ?
SmartDelegator 实现了 PropertyDelegateProvider 的 provideDelegate,根据属性名称动态返回委托属性。
class SmartDelegator : PropertyDelegateProvider<Owner, StringDelegate> {
override fun provideDelegate(thisRef: Owner, property: KProperty<*>): StringDelegate {
return if (property.name.contains("log")) {
StringDelegate("log")
} else {
StringDelegate("cat")
}
}
}
?
使用方法和委托属性一样:
class Owner {
var log by SmartDelegator()
var cat by SmartDelegator()
var text: String by StringDelegate()
var textReadWrite: String by StringReadWritePropertyDelegate()
}
fun main() {
println(owner.log)
println(owner.cat)
}
根据属性名称输出:
log
cat
总结
kotlin 使用 by 关键字实现了委托模式。kotlin 的委托有委托类、委托属性、懒加载委托、自定义委托。 ?
自定义委托有 ReadOnlyProperty、ReadWriteProperty、PropertyDelegateProvider 三个接口,方便快速实现委托属性。 ?
使用委托可以简化样板代码,写起来更加简洁。
|