ThinkSSL🔒 一键申购 5分钟快速签发 30天无理由退款 购买更放心 广告
# 委托属性 确定的通用属性,尽管我们可以在任何需要它们的时候手动地实现它们,但一次性实现并放在一个库里面应该会更好。使用场景包括: * 延迟属性:值仅仅在首次访问时才计算, * 可观察属性:监听关于属性改变的通知, * 在映射中存储属性,而不是在相互分享的字段中。 要覆盖这些(和其它的)场景,Kotlin 支持_委托属性_: ``` kotlin class Example { var p: String by Delegate() } ``` 语法是:`val/var <属性名>: <类型> by <表达式>`。其中 `by` 后面的表达式就是_委托_,因为属性相应的 `get()`(和 `set()`)将会委托给它的 `getValue()` 和 `setValue()` 方法。属性委托不用实现任何接口,但它们得提供一个 `getValue()` 函数(和 `setValue()` —— 给 `var` 的属性)。例如: ``` kotlin 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.'") } } ``` 当我们人 `p` 委托的 `Delegate` 实例读取时,就会从 `Delegate` 调用 `getValue()` 函数,所以它的第一个参数是我们要读取 `p` 的对象,而第二个参数保留一个 `p` 自身的描述(例如你可以带上它的名字)。例如: ``` kotlin val e = Example() println(e.p) ``` 会打印 ``` Example@33a17727, thank you for delegating ‘p’ to me! ``` 类似地,当我们赋值给 `p` 时,`setValue()` 函数就会被调用。前面两个参数是一样的,第三个存储要被赋予的值: ``` kotlin e.p = "NEW" ``` 会打印 ``` NEW has been assigned to ‘p’ in Example@33a17727. ``` ## 属性委托的条件 这里我们概述委托对象的必备条件 对于一个**只读**属性(意即一个 `val`),委托要提供一个名称为 `getValue` 的函数并有下列参数: * 接收者 —— 必须与_属性拥有者_相同或其子类(对于扩展属性 —— 类型也要是被扩展的), * 元数据 —— 必须是 `KProperty<*>` 类型或它的超类, 这个函数必须返回与属性相同(或它的超类)的类型。 对于可变属性(`var`),委托要_额外地_提供一个名为 `setValue` 的函数并有下列参数: * 接收者 —— 与 `getValue()` 相同, * 元数据 —— 与 `getValue()` 相同, * 新值 —— 必须与属性相同或是它的超类。 `getValue()` 和/或 `setValue()` 函数可以提供作为成员函数或扩展函数提供。后者在你需要委托属性给一个本来没有提供这些函数的对象时很方便。这两个函数都需要被 `operator` 关键字标记。 ## 标准委托 Kotlin 标准库为几种有用的委托提供了工厂方法。 ### 延迟 `lazy()` 是携带了 lambda 并返回一个 `Lazy<T>` 实例的函数,可充当延迟属性委托的实现:首次调用 `get()` 会执行 lambda 传递给 `lazy()` 并记住结果,其后对 `get()` 的调用简单地返回所记住的结果。 ``` kotlin val lazyValue: String by lazy { println("computed!") "Hello" } fun main(args: Array<String>) { println(lazyValue) println(lazyValue) } ``` 默认情况下,延迟属性的求值是*同步的**:仅在单个线程中求值,而且所有的线程都将看到相同的值。如果不要求委托初始化同步,那么把 `LazyThreadSafetyMode.PUBLICATION` 作为参数传递给 `lazy()` 函数,多个线程可同时执行。如果你确定初始化将始终发生在单个线程上,你可以使用 `LazyThreadSafetyMode.NONE` 模式,则不会有线程安全保证及相关开销。 ### 可观察 `Delegates.observable()` 带有两个实参:初始值和一个修改后的句柄。这个句柄会在任何我们要给属性赋值(在任务已经执行_之后_)时调用。它有三个参数:要被赋值的属性,旧值和新值: ``` kotlin import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second" } ``` 这个示例会打印 ``` <no name> -> first first -> second ``` 如果你要拦截一个任务并“否决”它,使用 `vetoable()` 代替 `observable()`。这个处理器在新属性的赋值完成_之前_传递给 `vetoable`。 ## 在映射中存储属性 一个通用的使用场景是存储属性的值在映射里面。这经常发生在诸如解析 JSON 或做其它“动态”东西的应用里面。在这种情况下,你可以为委托属性映射它自身的实例为委托。 ``` kotlin class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } ``` 在这个例子中,构造器带了一个映射: ``` kotlin val user = User(mapOf( "name" to "John Doe", "age" to 25 )) ``` 委托属性从这个映射中拿到值(通过字符串键 —— 属性的名称): ``` kotlin println(user.name) // 打印 "John Doe" println(user.age) // 打印 25 ``` 如果你使用 `MutableMap` 代替只读的 `Map`,那么也能在 `var` 属性上工作: ``` kotlin class MutableUser(val map: MutableMap<String, Any?>) { var name: String by map var age: Int by map } ```