代理属性

Kotlin 很多常用属性,虽然我们可以在每次需要的时候手动实现它们,但更好的办法是一次实现多次使用,并放到库里。比如:

延迟属性:只在第一次访问时计算它的值。
可观察属性:监听者从这获取这个属性更新的通知。
在 map 中存储的属性,而不是为每个属性创建一个字段。

为了满足这些情形,Kotllin 支持代理属性:

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

语法结构是: val/var <property name>: <Type> by <expression> 在 by 后面的表达式就是代理,因为get() set() 对应的属性会被 getValue() setValue() 方法代理。属性代理不需要任何接口的实现,但必须要提供 getValue() 函数(如果是 var 还需要 setValue())。像这样:

  1. class Delegate {
  2. operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
  3. return "$thisRef, thank you for delegating '${property.name}' to me!"
  4. }
  5. operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
  6. println("$value has been assigned to '${property.name} in $thisRef.'")
  7. }
  8. }

当我们从 p 也就 是一个 Delegate 实例进行读取操作时,会调用 DelegategetValue() 函数,因此第一个参数是我们从 p 中读取的,第二个参数是持有 p 的一个描述。比如:

  1. val e = Example()
  2. println(e.p)

打印结果: 

Example@33a17727, thank you for delegating ‘p’ to me!

同样当我们给 p 赋值时 setValue() 函数就将被调用。前俩个参数所以一样的,第三个持有分配的值:

  1. e.p = "NEW"

打印结果: 

NEW has been assigned to ‘p’ in Example@33a17727.

从 Kotlin 1.1 开始支持在函数内部或者代码块内声明代理,而不必是类成员。

标准代理

Kotlin 标准库为几种常用的代理提供了工厂方法

延迟

lazy() 是一个接受 lamdba 并返回一个实现延迟属性的代理:第一次调用 get() 执行 lamdba 并传递 lazy() 并存储结果,以后每次调用 get() 时只是简单返回之前存储的值。

  1. val lazyValue: String by lazy {
  2. println("computed!")
  3. "Hello"
  4. }
  5. fun main(args: Array<String>) {
  6. println(lazyValue)
  7. println(lazyValue)
  8. }

上面代码的输出是:

  1. computed!
  2. Hello
  3. Hello

默认情况下延迟属性的计算是同步的:该值的计算只在一个线程里,其他所有线程都将读取同样的值。如果代理不需要同步初始化,而且允许出现多线程同时执行该操作,可以传 LazyThreadSafetyMode.PUBLICATION 参数给 lazy() 。如果你确信初始化只会在单线程中出现,那么可以使用 LazyThreadSafetyMode.NONE 该模式不会提供任何线程安全相关的保障。

如果你想要线程安全,使用 blockingLazy(): 它还是按照同样的方式工作,但保证了它的值只会在一个线程中计算,并且所有的线程都获取的同一个值。

可观察属性

Delegates.observable() 需要两个参数:一个初始值和一个用于修改的 handler 。每次我们给属性赋值时都会调用handler (在初始赋值操作后)。它有三个参数:一个将被赋值的属性,旧值,新值:

  1. import kotlin.properties.Delegates
  2. class User {
  3. var name: String by Delegates.observable("<no name>") {
  4. prop, old, new ->
  5. println("$old -> $new")
  6. }
  7. }
  8. fun main(args: Array<String>) {
  9. val user = User()
  10. user.name = "first"
  11. user.name = "second"
  12. }

打印结果

\ -> first
first -> second

如果你想能够打断赋值并取消它,用 vetoable()代替 observable() 。传递给vetoable 的 handler 会在赋新值之前调用。

在 Map 中存储属性

把属性值存储在 map 中是一种常见的使用方式,这种操作经常出现在解析 JSON 或者其它动态的操作中。这种情况下你可以使用 map 来代理它的属性。

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

在这个例子中,构造函数接受一个 map :

  1. val user = User(mapOf(
  2. "name" to "John Doe",
  3. "age" to 25
  4. ))

代理属性将从这个 map 中取指(通过属性的名字):

  1. println(user.name) // Prints "John Doe"
  2. println(user.age) // Prints 25

var 属性可以用 MutableMap 代替只读的 Map

  1. class MutableUser(val map: MutableMap<String, Any?>) {
  2. var name: String by map
  3. var age: Int by map
  4. }

本地代理属性(从1.1开始支持)

你可以声明本地变量作为代理属性。比如你可以创建一个本地延迟变量:

  1. fun example(computeFoo: () -> Foo) {
  2. val memoizedFoo by lazy(computeFoo)
  3. if (someCondition && memoizedFoo.isValid()) {
  4. memoizedFoo.doSomething()
  5. }
  6. }

memoizedFoo 只会在第一次访问时求值。如果 someCondition 不符合,那么该变量将根本不会被计算。

属性代理的要求

这里总结一些代理对象的要求。

只读属性 (val),代理必须提供一个名字叫 getValue 的函数并接受如下参数:

thisRef接收者—必须是属性拥有者是同一种类型,或者是其父类

property 必须是 KProperty<*> 或这它的父类

这个函数必须返回同样的类型或子类作为属性。

可变属性 (var),代理必须添加一个叫 setValue 的函数并接受如下参数:

thisRefgetValue() 一样

propertygetValue() 一样
新值—必须和属性类型一致或是它的父类

getValue()setValue() 函数必须作为代理类的成员函数或者扩展函数。扩展函数对与想要对对象代理原本没有的函数是十分有用。两种 函数必须标记 operator 关键字。

代理类可能实现 ReadOnlyPropertyReadWriteProperty 中的一个并要求带有 operator 方法。这些接口在 Kotlin 标准库中有声明:

  1. interface ReadOnlyProperty<in R, out T> {
  2. operator fun getValue(thisRef: R, property: KProperty<*>): T
  3. }
  4. interface ReadWriteProperty<in R, T> {
  5. operator fun getValue(thisRef: R, property: KProperty<*>): T
  6. operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
  7. }

转换规则

在每个代理属性的实现的背后,Kotlin 编译器都会生成辅助属性并代理给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地代理给这个附加属性:

  1. class C {
  2. var prop: Type by MyDelegate()
  3. }
  4. // this code is generated by the compiler instead:
  5. class C {
  6. private val prop$delegate = MyDelegate()
  7. var prop: Type
  8. get() = prop$delegate.getValue(this, this::prop)
  9. set(value: Type) = prop$delegate.setValue(this, this::prop, value)
  10. }

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到类 C 外部的实例而 this::propKProperty 类型的反射对象,该对象描述 prop 自身。

注意,直接在代码中引用绑定的可调用引用的语法 this::prop 自 Kotlin 1.1 起才可用。

提供代理(自 1.1 起)

通过定义 provideDelegate 操作符,可以扩展创建属性实现所代理对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性代理实例。

provideDelegate 的一个可能的使用场景是在创建属性时检查属性一致性。

例如,如果你想要在绑定之前检查属性名称,可以这样写:

  1. class ResourceLoader<T>(id: ResourceID<T>) {
  2. operator fun provideDelegate(
  3. thisRef: MyUI,
  4. prop: KProperty<*>
  5. ): ReadOnlyProperty<MyUI, T> {
  6. checkProperty(thisRef, prop.name)
  7. // create delegate
  8. }
  9. private fun checkProperty(thisRef: MyUI, name: String) { ... }
  10. }
  11. fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
  12. class MyUI {
  13. val image by bindResource(ResourceID.image_id)
  14. val text by bindResource(ResourceID.text_id)
  15. }

provideDelegate 的参数与 getValue 相同:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型,
  • property —— 必须是类型 KProperty<*> 或其超类型。

在创建 MyUI 实例期间,为每个属性调用 provideDelegate 方法,并立即执行必要的验证。

如果没有这种打断属性与其代理之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名,这不是很方便:

  1. // Checking the property name without "provideDelegate" functionality
  2. class MyUI {
  3. val image by bindResource(ResourceID.image_id, "image")
  4. val text by bindResource(ResourceID.text_id, "text")
  5. }
  6. fun <T> MyUI.bindResource(
  7. id: ResourceID<T>,
  8. propertyName: String
  9. ): ReadOnlyProperty<MyUI, T> {
  10. checkProperty(this, propertyName)
  11. // create delegate
  12. }

在生成的代码中, provideDelegate 方法用来初始化辅助 prop$delegate 属性的初始化。 下面是属性声明 val prop: Type by MyDelegate() 生成的代码与 上面(当 provideDelegate 方法不存在时)生成的代码的对比:

  1. class C {
  2. var prop: Type by MyDelegate()
  3. }
  4. // this code is generated by the compiler
  5. // when the 'provideDelegate' function is available:
  6. class C {
  7. // calling "provideDelegate" to create the additional "delegate" property
  8. private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
  9. val prop: Type
  10. get() = prop$delegate.getValue(this, this::prop)
  11. }

注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。