Kotlin代理

Java中的代理很烦很繁,而Kotlin中的代理却看起来很简单。我们来学学Kotlin中代理的用法吧。

本文整理自Delegated Properties

有些通用类型的属性,尽管我们可以在需要每次都自己实现对于某些常用类型的属性,尽管我们可以在需要用于这些属性时每次都自行实现,但如果能一次性实现所有这些属性,并将其封装到库中可能是更好的方式。比如:

  • 懒加载属性: 这些属性的值在首次使用时才生成
  • observable属性:每当属性值发生变化时监听器会收到通知
  • 将属性值保存到map,而不是为每个属性定义一个单独的字段

代理

针对这类情形Kotlin提供了代理属性 ,语法是val/var <property name>: <Type> by <expression>by关键字后面的表达式即代理 。 属性对应的get()set()方法会被代理到<expression>对应对象的getValue()setValue()方法。属性代理不必实现任何接口,但必须满足以下条件:

  • val属性提供getValue()方法
  • var性提供getValue()setValue()方法

当然,Kotlin标准库中提供了ReadOnlyPropertyReadOnlyProperty接口包含这里提到的方法。Delegate可以实现这两个接口。

比如这里的Delegate就是一个代理:

1
2
3
4
5
6
7
8
9
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank 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.'")
}
}

假如有以下Example

1
2
3
class Example {
var p: String by Delegate()
}

当你读取Example.p字段的值时,由于它被代理到一个Deleage实例,这个Delegate实例的getValue()方法会被调用。getValue() 的参数如下:

  1. 第一个参数为Example实例,即你读取p字段时的那个实例
  2. 第二个参数是关于p的描述,它在Kotlin中被封装与KProperty对象

所以最终getValue()输出Example@33a17727, thank you for delegating ‘p’ to me!

注意:

  • getValue()方法参数的必须是thisRef: Example?, property: KProperty<*>thisRef的类型必须跟属性的类型相同,或者是属性类型的父类。也可以放宽为Any? ,这样能让 Delegate 更加通用。property必须为KProperty<*>或其父类
  • getValue()方法的返回值必须跟属性类型相同,或者是属性类型的父类
  • setValue()方法的第三个参数new value,必须跟属性类型相同或是其父类
  • getValue()setValue()可以是Delegate的成员方法,也可是其扩展方法。对于原先并没有定义这些方法的对象而言,扩展方法更为方便。但无论是作为成员方法还是扩展方法,都需要添加operator修饰符

为什么对getValue()setValue()方法有上述规定呢,我们看看Kotlin编译器是如何处理Delegate的。实际上,对于每个代理属性,Kotlin编译器都会生成一个辅助的属性。比如,对于prop属性,会生成一个隐藏的prop$delegate属性,而get()set()方法最后只是简单地代理到这个辅助属性。

1
2
3
4
5
6
7
8
9
10
11
class C {
var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

我们可以在Idea中通过Tools > Kotlin > Show Kotlin Bytecode > Decompile看到对应的Java代码,确实如这里所述。

标准代理

Lazy代理

lazy()是一个可接收lambda表达式作为参数的方法,它返回一个Lazy<T>实例,该实例作为代理来实现懒加载功能。第一次调用get()时会执行传给lazy()方法的lambda并且保留其结果,之后再调用get()时会直接返回该结果。缺省情况下对懒加载属性的计算是同步的

  • LazyThreadSafetyMode.SYNCHRONIZED - 默认的懒加载方式,会锁定当前属性以保证仅在一个线程中对其初始化
  • LazyThreadSafetyMode.PUBLICATION - 初始化方法在并发访问未被初始化的属性时可多次被调用,但仅最先被返回的值作为属性的值
  • LazyThreadSafetyMode.NONE - 对属性的访问不加任何锁。如果当前对象在多个线程中被访问,则属性的值不确定

Observable代理

Delegate.observable()有两个参数:属性的初始值和监听器。当给属性赋值时(即执行赋值操作后)监听器会被调用。监听器被调用时会接收到三个参数:被赋值的属性,属性的旧值,属性的新值。

vetoable()observable()类似,但它会在属性赋值前被调用。

Map

一个常用的场景是在map中保存属性值。这种场景在解析JSON或类似的”动态”编程时很常见。这时可以使用map实例作为属性的代理。类似这样:

1
2
3
4
5
6
7
8
9
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}

val user = User(mapOf(
"name" to "King"
"age" to 25
))

代理属性(例如name从map)中获取值,取值时的key即为属性的名字。