kotlin 學(xué)習(xí) (14)

委托屬性

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

延遲屬性(lazy properties): 其值只在首次訪問時(shí)計(jì)算;

可觀察屬性(observable properties): 監(jiān)聽器會收到有關(guān)此屬性變更的通知;

把多個(gè)屬性儲存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。

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

classExample{

varp:StringbyDelegate()

}

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

importkotlin.reflect.KProperty

?

classDelegate{

operatorfungetValue(thisRef:Any?,property:KProperty<*>):String{

return"$thisRef, thank you for delegating '${property.name}' to me!"

? ? }

operatorfunsetValue(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?的對象、第二個(gè)參數(shù)保存了對?p?自身的描述 (例如你可以取它的名字)。 例如:

vale=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.

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

請注意,自 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()?會執(zhí)行已傳遞給?lazy()?的 lambda 表達(dá)式并記錄結(jié)果, 后續(xù)調(diào)用?get()?只是返回記錄的結(jié)果。

vallazyValue:Stringbylazy{

println("computed!")

"Hello"

}

?

funmain() {

println(lazyValue)

println(lazyValue)

}

Target platform:?JVMRunning on kotlin v.?1.3.70

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

可觀察屬性 Observable

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

importkotlin.properties.Delegates

?

classUser{

varname:StringbyDelegates.observable("<no name>") {

prop,old,new->

println("$old -> $new")

? ? }

}

?

funmain() {

valuser=User()

user.name="first"

user.name="second"

}

Target platform:?JVMRunning on kotlin v.?1.3.70

如果你想截獲賦值并“否決”它們,那么使用?vetoable()?取代?observable()。 在屬性被賦新值生效之前會調(diào)用傳遞給?vetoable?的處理程序。

把屬性儲存在映射中

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

classUser(valmap:Map<String,Any?>) {

valname:Stringbymap

valage:Intbymap

}

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

valuser=User(mapOf(

"name"to"John Doe",

"age"to25

))

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

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

println(user.age)// Prints 25

Target platform:?JVMRunning on kotlin v.?1.3.70

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

classMutableUser(valmap:MutableMap<String,Any?>) {

varname:Stringbymap

varage:Intbymap

}

局部委托屬性(自 1.1 起)

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

funexample(computeFoo: ()->Foo) {

valmemoizedFoobylazy(computeFoo)

?

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

memoizedFoo.doSomething()

? ? }

}

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

屬性委托要求

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

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

thisRef?—— 必須與?屬性所有者?類型(對于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型;

property?—— 必須是類型?KProperty<*>?或其超類型。

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

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

thisRef?—— 同?getValue();

property?—— 同?getValue();

new value —— 必須與屬性同類型或者是它的子類型。

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

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

interfaceReadOnlyProperty<inR,outT>{

operatorfungetValue(thisRef:R,property:KProperty<*>):T

}

?

interfaceReadWriteProperty<inR,T>{

operatorfungetValue(thisRef:R,property:KProperty<*>):T

operatorfunsetValue(thisRef:R,property:KProperty<*>,value:T)

}

翻譯規(guī)則

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

classC{

varprop:TypebyMyDelegate()

}

?

// 這段是由編譯器生成的相應(yīng)代碼:

classC{

privatevalprop$delegate=MyDelegate()

varprop: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::prop?是?KProperty?類型的反射對象,該對象描述?prop?自身。

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

提供委托(自 1.1 起)

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

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

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

classResourceDelegate<T>:ReadOnlyProperty<MyUI,T>{

overridefungetValue(thisRef:MyUI,property:KProperty<*>):T{ ... }

}


classResourceLoader<T>(id:ResourceID<T>) {

operatorfunprovideDelegate(

thisRef:MyUI,

prop:KProperty<*>

):ReadOnlyProperty<MyUI,T>{

checkProperty(thisRef,prop.name)

// 創(chuàng)建委托

returnResourceDelegate()

? ? }

?

privatefuncheckProperty(thisRef:MyUI,name:String) {……}

}

?

classMyUI{

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

?

valimagebybindResource(ResourceID.image_id)

valtextbybindResource(ResourceID.text_id)

}

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

thisRef?—— 必須與?屬性所有者?類型(對于擴(kuò)展屬性——指被擴(kuò)展的類型)相同或者是它的超類型;

property?—— 必須是類型?KProperty<*>?或其超類型。

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

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

// 檢測屬性名稱而不使用“provideDelegate”功能

classMyUI{

valimagebybindResource(ResourceID.image_id,"image")

valtextbybindResource(ResourceID.text_id,"text")

}

?

fun<T>MyUI.bindResource(

id:ResourceID<T>,

propertyName:String

):ReadOnlyProperty<MyUI,T>{

checkProperty(this,propertyName)

// 創(chuàng)建委托

}

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

classC{

varprop:TypebyMyDelegate()

}

?

// 這段代碼是當(dāng)“provideDelegate”功能可用時(shí)

// 由編譯器生成的代碼:

classC{

// 調(diào)用“provideDelegate”來創(chuàng)建額外的“delegate”屬性

privatevalprop$delegate=MyDelegate().provideDelegate(this,this::prop)

varprop:Type

get()=prop$delegate.getValue(this,this::prop)

set(value:Type)=prop$delegate.setValue(this,this::prop,value)

}

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容