Kotlin 知識(shí)梳理系列文章
Kotlin 知識(shí)梳理(1) - Kotlin 基礎(chǔ)
Kotlin 知識(shí)梳理(2) - 函數(shù)的定義與調(diào)用
Kotlin 知識(shí)梳理(3) - 類佛点、對(duì)象和接口
Kotlin 知識(shí)梳理(4) - 數(shù)據(jù)類伪窖、類委托 及 object 關(guān)鍵字
Kotlin 知識(shí)梳理(5) - lambda 表達(dá)式和成員引用
Kotlin 知識(shí)梳理(6) - Kotlin 的可空性
Kotlin 知識(shí)梳理(7) - Kotlin 的類型系統(tǒng)
Kotlin 知識(shí)梳理(8) - 運(yùn)算符重載及其他約定
Kotlin 知識(shí)梳理(9) - 委托屬性
Kotlin 知識(shí)梳理(10) - 高階函數(shù):Lambda 作為形參或返回值
Kotlin 知識(shí)梳理(11) - 內(nèi)聯(lián)函數(shù)
Kotlin 知識(shí)梳理(12) - 泛型類型參數(shù)
一、本文概要
本文是對(duì)<<Kotlin in Action>>
的學(xué)習(xí)筆記第献,如果需要運(yùn)行相應(yīng)的代碼可以訪問(wèn)在線環(huán)境 try.kotlinlang.org囊拜,這部分的思維導(dǎo)圖為:
二、委托屬性的基本操作
2.1 委托屬性的基本語(yǔ)法
class Foo {
var p : Type by Delegate()
}
類型為Type
的屬性p
將它的訪問(wèn)器邏輯委托給了另一個(gè)Delegate
實(shí)例究孕,通過(guò)關(guān)鍵字by
對(duì)其后的 表達(dá)式求值 來(lái)獲取這個(gè)對(duì)象啥酱,關(guān)鍵字by
可以用于任何 符合屬性委托約定規(guī)則的對(duì)象。
按照約定厨诸,Delegate
類必須具有getValue
和setValue
方法镶殷,它們可以是成員函數(shù),也可以是擴(kuò)展函數(shù)微酬,Delegate
的簡(jiǎn)單實(shí)現(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
作為普通屬性使用時(shí)绘趋,實(shí)際上將調(diào)用Delegate
類型的輔助屬性的方法。為了研究這種機(jī)制如何在實(shí)踐中使用颗管,我們首先看一個(gè)委托屬性展示威力的例子:庫(kù)對(duì)惰性初始化的支持陷遮。
2.2 使用委托屬性:惰性初始化和 "by lazy()"
惰性初始化是一種常見(jiàn)的模式,直到 在第一次訪問(wèn)該屬性 的時(shí)候忙上,才根據(jù)需要?jiǎng)?chuàng)建對(duì)象的一部分拷呆。
2.2.1 使用支持屬性來(lái)實(shí)現(xiàn)惰性初始化
使用這種技術(shù)來(lái)實(shí)現(xiàn)惰性初始化時(shí),需要兩個(gè)值疫粥,一個(gè)是對(duì)內(nèi)部可見(jiàn)的可空_emails
變量茬斧,另一個(gè)是提供對(duì)屬性的讀取訪問(wèn)的email
變量,它是非空的梗逮,在email
的get()
函數(shù)中首先判斷_emails
變量是否為空项秉,如果為空那么就先初始化它,否則直接返回慷彤。
2.2.2 使用委托屬性來(lái)實(shí)現(xiàn)惰性初始化
class Person(val name : String) {
val emails by lazy { loadEmails(this) }
}
這里可以使用標(biāo)準(zhǔn)庫(kù)函數(shù)lazy
返回的委托娄蔼,lazy
函數(shù)返回一個(gè)對(duì)象,該對(duì)象具有一個(gè)名為getValue
且簽名正確的方法底哗,因此可以把它與by
關(guān)鍵字一起使用來(lái)創(chuàng)建一個(gè)委托屬性岁诉。lazy
的參數(shù)是一個(gè)lambda
,可以調(diào)用它來(lái)初始化這個(gè)值跋选,默認(rèn)情況下涕癣,lazy
函數(shù)是線程安全的。
2.3 實(shí)現(xiàn)委托屬性
2.3.1 常規(guī)實(shí)現(xiàn)方式
要了解委托屬性的實(shí)現(xiàn)方式前标,讓我們來(lái)看另一個(gè)例子:當(dāng)一個(gè)對(duì)象的屬性更改時(shí)通知監(jiān)聽(tīng)器坠韩。Java
具有用于此類通知的標(biāo)準(zhǔn)機(jī)制:PropertyChangeSupport
和PropertyChangeEvent
距潘。PropertyChangeSupport
類維護(hù)了一個(gè)監(jiān)聽(tīng)器列表,并向它們發(fā)送PropertyChangeEvent
事件只搁,要使用它音比,你通常需要把PropertyChangeSupport
的一個(gè)實(shí)例存儲(chǔ)為bean
類的一個(gè)字段,并將屬性更改的處理委托給它氢惋。
為了避免在每個(gè)類中去添加這個(gè)字段洞翩,你需要?jiǎng)?chuàng)建一個(gè)小的工具類,用來(lái)存儲(chǔ)PropertyChangeSupport
的實(shí)例并監(jiān)聽(tīng)屬性更改明肮,之后菱农,你的類會(huì)繼承這個(gè)工具類,以訪問(wèn)changeSupport
柿估。
open class ValueChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
//添加監(jiān)聽(tīng)者循未。
fun addListener(listener : PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
//移除監(jiān)聽(tīng)者。
fun removeListener(listener : PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
//輔助類秫舌,如果通過(guò)該輔助類改變了屬性的妖,那么將會(huì)通知監(jiān)聽(tīng)者。
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)聽(tīng)者足陨。
changeSupport.firePropertyChange(valueName, oldValue, newValue)
}
}
class Person(val name : String, age : Int) : ValueChangeAware() {
// _age 為輔助類的一個(gè)實(shí)例嫂粟。
val _age = ObservableValue("age", age, changeSupport)
//通過(guò)輔助類進(jìn)行讀寫操作。
var age : Int
get() = _age.getValue()
set(value) { _age.setValue(value) }
}
下面是實(shí)際應(yīng)用的代碼:
fun main(args: Array<String>) {
val person = Person("zemao", 20)
person.addListener(
//監(jiān)聽(tīng)者打印出改變的屬性名墨缘、原屬性值和新的屬性值星虹。
PropertyChangeListener { event ->
println("${event.propertyName} " +
"changed from ${event.oldValue} to ${event.newValue}" )
}
)
person.age = 18
}
運(yùn)行結(jié)果為:
>> age changed from 20 to 18
2.3.2 使用 ObservableValue 作為屬性委托
在上面的代碼中,如果Person
類中包含了多個(gè)與age
類似的屬性镊讼,那么就需要?jiǎng)?chuàng)建多個(gè)_age
的實(shí)例宽涌,并把getter
和setter
委托給它,Kotlin
的委托屬性可以讓你擺脫這些樣板代碼蝶棋,首先卸亮,我們需要重寫ObservableValue
代碼,讓它符合屬性委托的約定玩裙。
class ObservableValue (
var valueValue : Int,
val changeSupport : PropertyChangeSupport
) {
//按照約定的需要兼贸,用 operator 來(lái)標(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)聽(tīng)者溶诞。
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
和2.3.1
相比,我們做了以下幾點(diǎn)修改:
- 按照約定的需要决侈,
getValue
和setValue
函數(shù)被標(biāo)記了operator
很澄。 - 這些函數(shù)加了兩個(gè)參數(shù):一個(gè)用于接收屬性的實(shí)例,用來(lái)設(shè)置和讀取屬性,另一個(gè)用于表示屬性本身甩苛,這個(gè)屬性類型為
KProperty
,你可以使用KProperty.name
的方式來(lái)訪問(wèn)該屬性的名稱俏站。
下面讯蒲,我們?cè)傩薷?code>Person類,將age
屬性委托給ObservableValue
類:
class Person(val name : String, age : Int) : ValueChangeAware() {
var age : Int by ObservableValue(age, changeSupport)
}
運(yùn)行結(jié)果和2.3.1
相同肄扎。
2.3.3 使用 Delegates.observable 來(lái)實(shí)現(xiàn)屬性修改的通知
在Kotlin
標(biāo)準(zhǔn)庫(kù)中墨林,已經(jīng)包含了類似于ObservableValue
的類,因此我們不用手動(dòng)去實(shí)現(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)
}
運(yùn)行結(jié)果和以上兩小結(jié)相同旭等。
2.4 委托屬性的變換規(guī)則
讓我們來(lái)總結(jié)一下委托屬性是怎么工作的,假設(shè)你已經(jīng)有了一個(gè)具有委托屬性的類:
class Foo {
var p : Type by Delegate()
}
Delegate
實(shí)例將會(huì)被保存到一個(gè)隱藏的屬性中衡载,它被稱為<delegate>
搔耕,編譯器也將用一個(gè)KProperty
類型的對(duì)象來(lái)表示這個(gè)屬性,它被稱為<property>
痰娱,編譯器生成的的代碼如下:
class Foo {
private val <delegate> = Delegate()
var prop : Type {
get() = <delegate>.getValue(this, <property>)
set(value : Type) = <delegate>.setValue(this, <property>, value)
}
}
因此弃榨,在每個(gè)屬性訪問(wèn)器中,編譯器都會(huì)生成對(duì)應(yīng)的getValue
和setValue
方法梨睁。
2.5 在 map 中保存屬性的值
委托屬性發(fā)揮作用的另一種常見(jiàn)用法是 用在動(dòng)態(tài)定義的屬性集的對(duì)象中鲸睛,這樣的對(duì)象有時(shí)被稱為 自訂對(duì)象。例如考慮一個(gè)聯(lián)系人管理系統(tǒng)坡贺,可以用來(lái)存儲(chǔ)有關(guān)聯(lián)系人的任意信息官辈,系統(tǒng)中的每個(gè)人都有一些屬性需要特殊處理(例如名字),以及每個(gè)人特有的數(shù)量任意的額外屬性(例如遍坟,最小的孩子的生日)拳亿。
實(shí)現(xiàn)這種系統(tǒng)的一種方法是將人的所有屬性存儲(chǔ)在map
中,不確定提供屬性政鼠,來(lái)訪問(wèn)需要特殊處理的信息风瘦。
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)
}
因?yàn)闃?biāo)準(zhǔn)庫(kù)已經(jīng)在標(biāo)準(zhǔn)map
和MutableMap
接口上定義了getValue
和setValue
擴(kuò)展函數(shù)公般,所以可以在這里直接調(diào)用万搔。
更多文章,歡迎訪問(wèn)我的 Android 知識(shí)梳理系列:
- Android 知識(shí)梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個(gè)人主頁(yè):http://lizejun.cn
- 個(gè)人知識(shí)總結(jié)目錄:http://lizejun.cn/categories/