对比Java学Kotlin代理
Posted 陈蒙_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对比Java学Kotlin代理相关的知识,希望对你有一定的参考价值。
Java 代理
当我们需要对某个对外公开的 API 做一些拦截时,我们可以使用代理。常见的应用场景包括内部校验、将已废弃但是又无法删除的方法委托给新的代理类处理等(有点挂羊头卖狗肉的意思)。比如:
// 内部校验
public void invoke() {
if (mDelegateObject.isValid()) {
// omitted
}
}
// 将已废弃的方法委托给最新的代码
@Deprecated
public void deprecatedFunc() {
mDelegateObject.recommendedFunc() // 将老方法委托给新方法完成相关功能
}
但是如果外部引用的不是公共方法,而是直接引用了某个成员属性,这时事情就麻烦了,因为我们在 Java 中无法对属性完成委托。
从上面的例子中我们可以看出,代理对 Java 语言来说是一种设计模式,Java 语言本身并未提供委托相关的支持,而且使用 Java 无法完成对属性的委托。而我们今天的主角 Kotlin 则对上述问题做了改进。
Kotlin 代理
在 Kotlin 中,我们使用关键字 by
使用委托功能。
代理方法
Kotlin 对方法的委托是借助接口来实现的。具体用法如下:
interface Base {
fun print()
}
class BaseImpl(val i : Int) : Base {
override fun print() {
println(i)
}
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
我们来解读下上面的代码。首先,Base 是个接口,而 Derived 这个类继承了这个接口,看起来也没有实现 print()
方法,编译器之所以没报错,是因为我们使用了 by
关键字将其本应实现的 print()
方法委托给了 b,即最终执行的是 b.print()
。
注意,只支持接口,不支持抽象类。
代理属性
Kotlin 不仅支持方法代理,还支持属性代理。我们知道对类成员属性的操作无非就是读写两种。我们可以对类属性的读写过程进行拦截以实现委托功能。
其实说到对属性的拦截,我们回忆一下 Kotlin 属性是有默认的 getter&setter 的,我们重写这两个方法也可以实现对属性读写操作的拦截,也能实现委托功能。当我们只有一个属性时这种用法还好,但是当我们面对多个属性都要重写其 getter&setter 时难免会产生模板代码,而 Kotlin 提供的委托能力则可以避免这种问题的发生。
自定义委托
我们要对类属性的读写操作实现委托,只要重写两个接口即可:
public interface ReadOnlyProperty<in R, out T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
}
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
thisRef
是被代理属性所在类的类型,getValue()
的返回值类型要和被代理的属性类型一致(相同或子类型)。
从 ReadOnlyProperty
和 ReadWriteProperty
命名上也可以看出,分别对应只读类型(val)和可变类型(var)。我们定义一个实现上述接口的代理类,然后使用 by
关键字连接代理类实例即可:
class MyDelegate : ReadWriteProperty<Demo, String> {
override operator fun getValue(thisRef: Demo, property: KProperty<*>): String {
return "hi, $thisRef, thank you for delegating '${property.name}' to me!"
}
override operator fun setValue(thisRef: Demo, property: KProperty<*>, value: String) {
println("hi, $value has been assigned to '${property.name}' in $thisRef.")
}
}
class Demo {
var d: String by MyDelegate()
}
fun main() {
/* 输出结果:
** hi, xx has been assigned to 'd' in Demo@214c265e.
** hi, Demo@214c265e, thank you for delegating 'd' to me!
*/
val d = Demo()
d.d = "xx"
print(d.d)
}
当然,我们也忽略这两个接口,然后自定义类来重写 getValue() 和 setValue() 方法,也是可行的:
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thankkkk 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.")
}
}
class Demo {
var d: String by MyDelegate()
}
fun main() {
val d = Demo()
d.d = "xx"
print(d.d)
}
究其原理,是编译器将属性的 getter&setter 方法委托给了操作符方法 getValue()&setValue()。
其实委托不仅限于类的成员属性,top-level 类型的变量和局部变量也是适用的,只不过此时的 thisRef
是 null:
import kotlin.reflect.KProperty
import kotlin.properties.ReadWriteProperty
var topLevelProp : String by Delegate()
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thankkkk 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.")
}
}
fun main() {
topLevelProp = "yyy" // 输出 yyy has been assigned to 'topLevelProp' in null.
}
内置委托
除了自定义的委托能力,Kotlin 还根据常见的场景给我们内置了一些针对属性的标准的委托功能:
- 懒加载,关键字
by lazy
,只在首次引用的时候初始化一次,后续引用均使用初始化后的结果; - 监听功能,当可变变量发生改变时通知监听者;
- 将属性存在map中,将变量值与map中的key-value值对应起来,方便读取;
【懒加载】
对于 by lazy
,lazy 是 Kotlin 标准库里面的函数,入参是一个线程安全模式和 lambda 表达式。线程安全模式有LazyThreadSafetyMode.SYNCHRONIZED
(默认值)、LazyThreadSafetyMode.PUBLICATION
和 LazyThreadSafetyMode.NONE
。
val lazyValue: String by lazy (LazyThreadSafetyMode.SYNCHRONIZED, {
val l = System.currentTimeMillis()
l.toString()
})
fun main() {
println(lazyValue) // 输出值是 1634459597319
repeat(10000) {} // 经历一段耗时操作
println(lazyValue) // 输出值仍然是首次初始化的结果 // 输出值是 1634459597319
}
【监听属性变动】
我们可以用 kotlin.properties.Delegates.observable() 方法来监听属性的变化,这样可以在值发生改变之后获得回调,属于 afterChange,observable() 方法接受的入参有两个:初始值和一段可执行代码;如果我们想在属性值发生之前就获取通知,可以用 kotlin.properties.Delegates.vetoable()。
import kotlin.properties.Delegates
class Demo {
var prop: String by Delegates.observable("no value", {
_, old, new -> println("$old -> $new")
})
var veto: String by Delegates.vetoable("no value") { _, _, new ->
new.isNotEmpty()
}
}
fun main() {
val d = Demo()
d.prop = "hello, Delegates.observable"
d.veto = ""
print(d.veto) // 输出 no value
}
kotlin.properties.Delegates.vetoable()
方法接受两个入参,首先是属性的初始值 “no value”,然后是一个 lambda 表达式,其返回值是一个布尔值,true 表示对属性值的修改成功,否则表示不允许修改,其值保持不变,我们示例代码中就是不让 veto 属性接受空值,如果是发现外部赋值为空则直接忽略。
查看 observable() 和 veto() 的源码我们可以发现其实二者都是包装 kotlin.properties.ObservableProperty
实现的,我们也可以直接使用这个抽象类来完成对属性变动的监听。
【属性存储在map中】
Kotlin 还提供了一种让类的成员属性正向map中的key-value的功能,我们来看下:
data class User(val map: Map<String, Any?>) { // Map
val name by map // val
val age by map // val
}
fun main() {
val user = User(mapOf("name" to "linus", "age" to 99)) // mapOf()
print("name: ${user.name}, age: ${user.age}")
}
上述代码的输出结果为 “name: linus, age: 99”。
注意,User.name 和 User.age 都是只读类型的属性,这个只读类型的 Map 是对应的。如果属性是可变类型的,则 map 类型需要是可变类型的 MutableMap:
data class User(val map: MutableMap<String, Any?>) { // MutableMap
var name by map // var
var age by map // var
}
fun main() {
val user = User(mutableMapOf("name" to "linus", "age" to 99)) // mutableMapOf
print("name: ${user.name}, age: ${user.age}")
}
属性之间委托
Kotlin 还支持直接将某个属性委托给另外一个属性,这里的属性类别包括top-level属性、类的成员属性、类的扩展属性,两个属性可以上述类别中的任何一个。具体的用法是 by
关键字和操作符::
,比如:
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
以上是关于对比Java学Kotlin代理的主要内容,如果未能解决你的问题,请参考以下文章