Kotlin 知識梳理系列文章
Kotlin 知識梳理(1) - Kotlin 基礎(chǔ)
Kotlin 知識梳理(2) - 函數(shù)的定義與調(diào)用
Kotlin 知識梳理(3) - 類、對象和接口
Kotlin 知識梳理(4) - 數(shù)據(jù)類、類委托 及 object 關(guān)鍵字
Kotlin 知識梳理(5) - lambda 表達式和成員引用
Kotlin 知識梳理(6) - Kotlin 的可空性
Kotlin 知識梳理(7) - Kotlin 的類型系統(tǒng)
Kotlin 知識梳理(8) - 運算符重載及其他約定
Kotlin 知識梳理(9) - 委托屬性
Kotlin 知識梳理(10) - 高階函數(shù):Lambda 作為形參或返回值
Kotlin 知識梳理(11) - 內(nèi)聯(lián)函數(shù)
Kotlin 知識梳理(12) - 泛型類型參數(shù)
一、本文概要
本文是對<<Kotlin in Action>>的學(xué)習(xí)筆記,如果需要運行相應(yīng)的代碼可以訪問在線環(huán)境 try.kotlinlang.org,這部分的思維導(dǎo)圖為:

二、委托屬性的基本操作
2.1 委托屬性的基本語法
class Foo {
var p : Type by Delegate()
}
類型為Type的屬性p將它的訪問器邏輯委托給了另一個Delegate實例,通過關(guān)鍵字by對其后的 表達式求值 來獲取這個對象,關(guān)鍵字by可以用于任何 符合屬性委托約定規(guī)則的對象。
按照約定,Delegate類必須具有getValue和setValue方法,它們可以是成員函數(shù),也可以是擴展函數(shù),Delegate的簡單實現(xiàn)如下:
class Delegate {
operator fun getValue(...) { ... }
operator fun setValue(..., value : Type) { ... }
}
使用方法如下:
val foo = Foo()
val oldValue = foo.p
foo.p = newValue
當(dāng)我們將foo.p作為普通屬性使用時,實際上將調(diào)用Delegate類型的輔助屬性的方法。為了研究這種機制如何在實踐中使用,我們首先看一個委托屬性展示威力的例子:庫對惰性初始化的支持。
2.2 使用委托屬性:惰性初始化和 "by lazy()"
惰性初始化是一種常見的模式,直到 在第一次訪問該屬性 的時候,才根據(jù)需要創(chuàng)建對象的一部分。
2.2.1 使用支持屬性來實現(xiàn)惰性初始化
使用這種技術(shù)來實現(xiàn)惰性初始化時,需要兩個值,一個是對內(nèi)部可見的可空_emails變量,另一個是提供對屬性的讀取訪問的email變量,它是非空的,在email的get()函數(shù)中首先判斷_emails變量是否為空,如果為空那么就先初始化它,否則直接返回。
2.2.2 使用委托屬性來實現(xiàn)惰性初始化
class Person(val name : String) {
val emails by lazy { loadEmails(this) }
}
這里可以使用標(biāo)準(zhǔn)庫函數(shù)lazy返回的委托,lazy函數(shù)返回一個對象,該對象具有一個名為getValue且簽名正確的方法,因此可以把它與by關(guān)鍵字一起使用來創(chuàng)建一個委托屬性。lazy的參數(shù)是一個lambda,可以調(diào)用它來初始化這個值,默認(rèn)情況下,lazy函數(shù)是線程安全的。
2.3 實現(xiàn)委托屬性
2.3.1 常規(guī)實現(xiàn)方式
要了解委托屬性的實現(xiàn)方式,讓我們來看另一個例子:當(dāng)一個對象的屬性更改時通知監(jiān)聽器。Java具有用于此類通知的標(biāo)準(zhǔn)機制:PropertyChangeSupport和PropertyChangeEvent。PropertyChangeSupport類維護了一個監(jiān)聽器列表,并向它們發(fā)送PropertyChangeEvent事件,要使用它,你通常需要把PropertyChangeSupport的一個實例存儲為bean類的一個字段,并將屬性更改的處理委托給它。
為了避免在每個類中去添加這個字段,你需要創(chuàng)建一個小的工具類,用來存儲PropertyChangeSupport的實例并監(jiān)聽屬性更改,之后,你的類會繼承這個工具類,以訪問changeSupport。
open class ValueChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
//添加監(jiān)聽者。
fun addListener(listener : PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
//移除監(jiān)聽者。
fun removeListener(listener : PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
//輔助類,如果通過該輔助類改變了屬性,那么將會通知監(jiān)聽者。
class ObservableValue (
val valueName : String, var valueValue : Int,
val changeSupport : PropertyChangeSupport
) {
fun getValue() : Int = valueValue
fun setValue(newValue : Int) {
val oldValue = valueValue
valueValue = newValue
//通知監(jiān)聽者。
changeSupport.firePropertyChange(valueName, oldValue, newValue)
}
}
class Person(val name : String, age : Int) : ValueChangeAware() {
// _age 為輔助類的一個實例。
val _age = ObservableValue("age", age, changeSupport)
//通過輔助類進行讀寫操作。
var age : Int
get() = _age.getValue()
set(value) { _age.setValue(value) }
}
下面是實際應(yīng)用的代碼:
fun main(args: Array<String>) {
val person = Person("zemao", 20)
person.addListener(
//監(jiān)聽者打印出改變的屬性名、原屬性值和新的屬性值。
PropertyChangeListener { event ->
println("${event.propertyName} " +
"changed from ${event.oldValue} to ${event.newValue}" )
}
)
person.age = 18
}
運行結(jié)果為:
>> age changed from 20 to 18
2.3.2 使用 ObservableValue 作為屬性委托
在上面的代碼中,如果Person類中包含了多個與age類似的屬性,那么就需要創(chuàng)建多個_age的實例,并把getter和setter委托給它,Kotlin的委托屬性可以讓你擺脫這些樣板代碼,首先,我們需要重寫ObservableValue代碼,讓它符合屬性委托的約定。
class ObservableValue (
var valueValue : Int,
val changeSupport : PropertyChangeSupport
) {
//按照約定的需要,用 operator 來標(biāo)記,并添加了 KProperty。
operator fun getValue(p : Person, prop : KProperty<*>) : Int = valueValue
operator fun setValue(p : Person, prop : KProperty<*>, newValue : Int) {
val oldValue = valueValue
valueValue = newValue
//通知監(jiān)聽者。
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
和2.3.1相比,我們做了以下幾點修改:
- 按照約定的需要,
getValue和setValue函數(shù)被標(biāo)記了operator。 - 這些函數(shù)加了兩個參數(shù):一個用于接收屬性的實例,用來設(shè)置和讀取屬性,另一個用于表示屬性本身,這個屬性類型為
KProperty,你可以使用KProperty.name的方式來訪問該屬性的名稱。
下面,我們再修改Person類,將age屬性委托給ObservableValue類:
class Person(val name : String, age : Int) : ValueChangeAware() {
var age : Int by ObservableValue(age, changeSupport)
}
運行結(jié)果和2.3.1相同。
2.3.3 使用 Delegates.observable 來實現(xiàn)屬性修改的通知
在Kotlin標(biāo)準(zhǔn)庫中,已經(jīng)包含了類似于ObservableValue的類,因此我們不用手動去實現(xiàn)可觀察的屬性邏輯,下面我們重寫Person類:
class Person(
val name: String, age: Int
) : ValueChangeAware() {
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(age, observer)
}
運行結(jié)果和以上兩小結(jié)相同。
2.4 委托屬性的變換規(guī)則
讓我們來總結(jié)一下委托屬性是怎么工作的,假設(shè)你已經(jīng)有了一個具有委托屬性的類:
class Foo {
var p : Type by Delegate()
}
Delegate實例將會被保存到一個隱藏的屬性中,它被稱為<delegate>,編譯器也將用一個KProperty類型的對象來表示這個屬性,它被稱為<property>,編譯器生成的的代碼如下:
class Foo {
private val <delegate> = Delegate()
var prop : Type {
get() = <delegate>.getValue(this, <property>)
set(value : Type) = <delegate>.setValue(this, <property>, value)
}
}
因此,在每個屬性訪問器中,編譯器都會生成對應(yīng)的getValue和setValue方法。
2.5 在 map 中保存屬性的值
委托屬性發(fā)揮作用的另一種常見用法是 用在動態(tài)定義的屬性集的對象中,這樣的對象有時被稱為 自訂對象。例如考慮一個聯(lián)系人管理系統(tǒng),可以用來存儲有關(guān)聯(lián)系人的任意信息,系統(tǒng)中的每個人都有一些屬性需要特殊處理(例如名字),以及每個人特有的數(shù)量任意的額外屬性(例如,最小的孩子的生日)。
實現(xiàn)這種系統(tǒng)的一種方法是將人的所有屬性存儲在map中,不確定提供屬性,來訪問需要特殊處理的信息。
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
//把 map 作為委托屬性。
val name: String by _attributes
}
使用方式:
fun main(args: Array<String>) {
val p = Person()
val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name)
}
因為標(biāo)準(zhǔn)庫已經(jīng)在標(biāo)準(zhǔn)map和MutableMap接口上定義了getValue和setValue擴展函數(shù),所以可以在這里直接調(diào)用。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.itdecent.cn/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/