鍍金池/ 教程/ Android/ Kotlin委托屬性
Kotlin內(nèi)聯(lián)函數(shù)
Kotlin開發(fā)環(huán)境設(shè)置(Eclipse)
Kotlin調(diào)用Java代碼
Kotlin使用Ant
Kotlin編譯器插件
Kotlin相等性
Kotlin JavaScript模塊
編寫Kotlin代碼文檔
Kotlin返回和跳轉(zhuǎn)
Kotlin異常處理
Kotlin可見性修飾符
Kotlin委托
Kotlin委托屬性
Kotlin編碼約定/編碼風(fēng)格
Kotlin基礎(chǔ)語法
使用Kotlin進(jìn)行服務(wù)器端開發(fā)
Kotlin接口
Kotlin反射
Kotlin類型別名
Kotlin枚舉類
Kotlin當(dāng)前版本是多少?
Kotlin注解處理工具
Kotlin類型的檢查與轉(zhuǎn)換
Kotlin屬性和字段
Kotlin類型安全的構(gòu)建器
Kotlin相比Java語言有哪些優(yōu)點(diǎn)?
Kotlin JavaScript反射
Kotlin 是什么?
Kotlin泛型
Kotlin慣用語法
Kotlin與OSGi
Kotlin數(shù)據(jù)類型
Kotlin是面向?qū)ο筮€是函數(shù)式語言?
Kotlin動(dòng)態(tài)類型
Kotlin協(xié)程
Kotlin操作符符重載
Kotlin使用Gradle
Kotlin密封類
Kotlin兼容性
Kotlin集合
Kotlin調(diào)用JavaScript
Kotlin null值安全
Kotlin函數(shù)
Kotlin開發(fā)環(huán)境設(shè)置(IntelliJ IDEA)
Kotlin嵌套類
Kotlin控制流程
Kotlin和Java語言比較
Kotlin 與 Java 語言兼容嗎?
Kotlin教程
Kotlin類和繼承
Kotlin對(duì)象表達(dá)式和對(duì)象聲明
JavaScript中調(diào)用Kotlin
Kotlin區(qū)間/范圍
Kotlin數(shù)據(jù)類
Kotlin lambda表達(dá)式
Kotlin是免費(fèi)的嗎?
Kotlin包
使用Kotlin進(jìn)行Android開發(fā)
在Java中調(diào)用Kotlin代碼
Kotlin this表達(dá)式
使用Kotlin進(jìn)行JavaScript開發(fā)
Kotlin擴(kuò)展
Kotlin解構(gòu)聲明
Kotlin注解
Kotlin使用Maven

Kotlin委托屬性

有一些常見的屬性類型,雖然我們可以在每次需要的時(shí)候手動(dòng)實(shí)現(xiàn)它們,
但是如果能夠?yàn)榇蠹野阉麄冎粚?shí)現(xiàn)一次并放入一個(gè)庫會(huì)更好。例如包括

  • 延遲屬性(lazy properties): 其值只在首次訪問時(shí)計(jì)算,
  • 可觀察屬性(observable properties): 監(jiān)聽器會(huì)收到有關(guān)此屬性變更的通知,
  • 把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。

為了涵蓋這些(以及其他)情況,Kotlin 支持 委托屬性:

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

語法是: val/var <屬性名>: <類型> by <表達(dá)式>。在 by{:.keyword} 后面的表達(dá)式是該 委托,
因?yàn)閷傩詫?duì)應(yīng)的 get()(和 set())會(huì)被委托給它的 getValue()setValue() 方法。
屬性的委托不必實(shí)現(xiàn)任何的接口,但是需要提供一個(gè) getValue() 函數(shù)(和 setValue()——對(duì)于 var{:.keyword} 屬性)。
例如:

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.'")
    }
}

當(dāng)我們從委托到一個(gè) Delegate 實(shí)例的 p 讀取時(shí),將調(diào)用 Delegate 中的 getValue() 函數(shù),
所以它第一個(gè)參數(shù)是讀出 p 的對(duì)象、第二個(gè)參數(shù)保存了對(duì) p 自身的描述
(例如你可以取它的名字)。 例如:

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

輸出結(jié)果:

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

類似地,當(dāng)我們給 p 賦值時(shí),將調(diào)用 setValue() 函數(shù)。前兩個(gè)參數(shù)相同,第三個(gè)參數(shù)保存將要被賦予的值:

e.p = "NEW"

輸出結(jié)果:

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

委托對(duì)象的要求規(guī)范可以在下文找到。

請(qǐng)注意,自 Kotlin 1.1 起你可以在函數(shù)或代碼塊中聲明一個(gè)委托屬性,因此它不一定是類的成員。
你可以在下文找到其示例)。

標(biāo)準(zhǔn)委托

Kotlin 標(biāo)準(zhǔn)庫為幾種有用的委托提供了工廠方法。

延遲屬性 Lazy

lazy() 是接受一個(gè) lambda 并返回一個(gè) Lazy <T> 實(shí)例的函數(shù),返回的實(shí)例可以作為實(shí)現(xiàn)延遲屬性的委托:
第一次調(diào)用 get() 會(huì)執(zhí)行已傳遞給 lazy() 的 lamda 表達(dá)式并記錄結(jié)果,
后續(xù)調(diào)用 get() 只是返回記錄的結(jié)果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

這個(gè)例子輸出:

computed!
Hello
Hello

默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個(gè)線程中計(jì)算,并且所有線程
會(huì)看到相同的值。如果初始化委托的同步鎖不是必需的,這樣多個(gè)線程
可以同時(shí)執(zhí)行,那么將 LazyThreadSafetyMode.PUBLICATION 作為參數(shù)傳遞給 lazy() 函數(shù)。
而如果你確定初始化將總是發(fā)生在單個(gè)線程,那么你可以使用 LazyThreadSafetyMode.NONE 模式,
它不會(huì)有任何線程安全的保證和相關(guān)的開銷。

可觀察屬性 Observable

Delegates.observable() 接受兩個(gè)參數(shù):初始值和修改時(shí)處理程序(handler)。
每當(dāng)我們給屬性賦值時(shí)會(huì)調(diào)用該處理程序(在賦值執(zhí)行)。它有三個(gè)
參數(shù):被賦值的屬性、舊值和新值:

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"
}

這個(gè)例子輸出:

<no name> -> first
first -> second

如果你想能夠截獲一個(gè)賦值并“否決”它,就使用 vetoable() 取代 observable()
在屬性被賦新值生效之前會(huì)調(diào)用傳遞給 vetoable 的處理程序。

把屬性儲(chǔ)存在映射中

一個(gè)常見的用例是在一個(gè)映射(map)里存儲(chǔ)屬性的值。
這經(jīng)常出現(xiàn)在像解析 JSON 或者做其他“動(dòng)態(tài)”事情的應(yīng)用中。
在這種情況下,你可以使用映射實(shí)例自身作為委托來實(shí)現(xiàn)委托屬性。

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

在這個(gè)例子中,構(gòu)造函數(shù)接受一個(gè)映射參數(shù):

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

委托屬性會(huì)從這個(gè)映射中取值(通過字符串鍵——屬性的名稱):

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

這也適用于 var{:.keyword} 屬性,如果把只讀的 Map 換成 MutableMap 的話:

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

局部委托屬性(自 1.1 起)

你可以將局部變量聲明為委托屬性。
例如,你可以使一個(gè)局部變量惰性初始化:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

memoizedFoo 變量只會(huì)在第一次訪問時(shí)計(jì)算。
如果 someCondition 失敗,那么該變量根本不會(huì)計(jì)算。

屬性委托要求

這里我們總結(jié)了委托對(duì)象的要求。

對(duì)于一個(gè)只讀屬性(即 val{:.keyword} 聲明的),委托必須提供一個(gè)名為 getValue 的函數(shù),該函數(shù)接受以下參數(shù):

  • thisRef —— 必須與 屬性所有者 類型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型,
  • property —— 必須是類型 KProperty<*> 或其超類型,

這個(gè)函數(shù)必須返回與屬性相同的類型(或其子類型)。

對(duì)于一個(gè)可變屬性(即 var{:.keyword} 聲明的),委托必須額外提供一個(gè)名為 setValue 的函數(shù),該函數(shù)接受以下參數(shù):

  • thisRef —— 同 getValue(),
  • property —— 同 getValue()
  • new value —— 必須和屬性同類型或者是它的超類型。

getValue() 或/和 setValue() 函數(shù)可以通過委托類的成員函數(shù)提供或者由擴(kuò)展函數(shù)提供。
當(dāng)你需要委托屬性到原本未提供的這些函數(shù)的對(duì)象時(shí)后者會(huì)更便利。
兩函數(shù)都需要用 operator 關(guān)鍵字來進(jìn)行標(biāo)記。

委托類可以實(shí)現(xiàn)包含所需 operator 方法的 ReadOnlyPropertyReadWriteProperty 接口之一。
這倆接口是在 Kotlin 標(biāo)準(zhǔn)庫中聲明的:

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

翻譯規(guī)則

在每個(gè)委托屬性的實(shí)現(xiàn)的背后,Kotlin 編譯器都會(huì)生成輔助屬性并委托給它。
例如,對(duì)于屬性 prop,生成隱藏屬性 prop$delegate,而訪問器的代碼只是簡(jiǎn)單地委托給這個(gè)附加屬性:

class C {
    var prop: Type by MyDelegate()
}

// 這段是由編譯器生成的相應(yīng)代碼:
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)
}

Kotlin 編譯器在參數(shù)中提供了關(guān)于 prop 的所有必要信息:第一個(gè)參數(shù) this 引用到外部類 C 的實(shí)例而 this::propKProperty 類型的反射對(duì)象,該對(duì)象描述 prop 自身。

請(qǐng)注意,直接在代碼中引用綁定的可調(diào)用引用)的語法 this::prop 自 Kotlin 1.1 起才可用。

提供委托(自 1.1 起)

通過定義 provideDelegate 操作符,可以擴(kuò)展創(chuàng)建屬性實(shí)現(xiàn)所委托對(duì)象的邏輯。
如果 by 右側(cè)所使用的對(duì)象將 provideDelegate 定義為成員或擴(kuò)展函數(shù),那么會(huì)調(diào)用該函數(shù)來
創(chuàng)建屬性委托實(shí)例。

provideDelegate 的一個(gè)可能的使用場(chǎng)景是在創(chuàng)建屬性時(shí)(而不僅在其 getter 或 setter 中)檢查屬性一致性。

例如,如果要在綁定之前檢查屬性名稱,可以這樣寫:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 創(chuàng)建委托
    }

    private fun checkProperty(thisRef: MyUI, name: String) { …… }
}

fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }

class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 的參數(shù)與 getValue 相同:

  • thisRef —— 必須與 屬性所有者 類型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型,
  • property —— 必須是類型 KProperty<*> 或其超類型。

在創(chuàng)建 MyUI 實(shí)例期間,為每個(gè)屬性調(diào)用 provideDelegate 方法,并立即執(zhí)行必要的驗(yàn)證。

如果沒有這種攔截屬性與其委托之間的綁定的能力,為了實(shí)現(xiàn)相同的功能,
你必須顯式傳遞屬性名,這不是很方便:

// 檢查屬性名稱而不使用“provideDelegate”功能
class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
}

fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // 創(chuàng)建委托
}

在生成的代碼中,會(huì)調(diào)用 provideDelegate 方法來初始化輔助的 prop$delegate 屬性。
比較對(duì)于屬性聲明 val prop: Type by MyDelegate() 生成的代碼與
上面(當(dāng) provideDelegate 方法不存在時(shí))生成的代碼:

class C {
    var prop: Type by MyDelegate()
}

// 這段代碼是當(dāng)“provideDelegate”功能可用時(shí)
// 由編譯器生成的代碼:
class C {
    // 調(diào)用“provideDelegate”來創(chuàng)建額外的“delegate”屬性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

請(qǐng)注意,provideDelegate 方法只影響輔助屬性的創(chuàng)建,并不會(huì)影響為 getter 或 setter 生成的代碼。


上一篇:Kotlin嵌套類下一篇:Kotlin屬性和字段