# 委托屬性
確定的通用屬性,盡管我們可以在任何需要它們的時候手動地實現它們,但一次性實現并放在一個庫里面應該會更好。使用場景包括:
* 延遲屬性:值僅僅在首次訪問時才計算,
* 可觀察屬性:監聽關于屬性改變的通知,
* 在映射中存儲屬性,而不是在相互分享的字段中。
要覆蓋這些(和其它的)場景,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
}
```