Kotlin 入門(mén)(四):委托

委托

類(lèi)委托

類(lèi) Derived 可以繼承一個(gè)接口 Base ,并將其所有共有的方法委托給一個(gè)指定的對(duì)象:

interface   Base    {
fun print()
 }
class   BaseImpl(val    x:  Int)    :   Base
{
override    fun print()
{   
print(x)    
}
 }
class   Derived(b:Base):Base    by  b
fun main(args:  Array<String>)  {   
val b   =   BaseImpl(10)            
Derived(b).print()  
//  輸出  10 
}

Derived 的超類(lèi)型列表中的 by -子句表示 b 將會(huì)在 Derived 中內(nèi)部存儲(chǔ)。 并且編譯器將 生成轉(zhuǎn)發(fā)給 b 的所有 Base 的方法

委托屬性

有一些常見(jiàn)的屬性類(lèi)型,雖然我們可以在每次需要的時(shí)候手動(dòng)實(shí)現(xiàn)它們, 但是如果能夠?yàn)榇?家把他們只實(shí)現(xiàn)一次并放入一個(gè)庫(kù)會(huì)更好。例如包括
1.延遲屬性(lazy properties): 其值只在首次訪問(wèn)時(shí)計(jì)算,
2.可觀察屬性(observable properties): 監(jiān)聽(tīng)器會(huì)收到有關(guān)此屬性變更的通知,
3.把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。
為了涵蓋這些(以及其他)情況,Kotlin 支持 委托屬性:

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

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

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!

類(lèi)似地,當(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.

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

Kotlin 標(biāo)準(zhǔn)庫(kù)為幾種有用的委托提供了工廠方法。
1.延遲屬性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)的開(kāi)銷(xiāo)。

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

輸出:
<no name>   ->  first 
first   ->  second

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

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

一個(gè)常見(jiàn)的用例是在一個(gè)映射(map)里存儲(chǔ)屬性的值。 這經(jīng)常出現(xiàn)在像解析 JSON 或者做 其他“動(dòng)態(tài)”事情的應(yīng)用中。 在這種情況下,你可以使用映射實(shí)例自身作為委托來(lái)實(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è)映射中取值(通過(guò)字符串鍵——屬性的名稱(chēng)):

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

局部委托屬性(自 1.1 起)

fun example(computeFoo: ()  ->  Foo)    {
val memoizedFoo by  lazy(computeFoo)
if(someCondition    &&  memoizedFoo.isValid())  {
       memoizedFoo.doSomething()    
}
 }

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

屬性委托要求

1.對(duì)于一個(gè)只讀屬性(即 val 聲明的),委托必須提供一個(gè)名為 getValue 的函數(shù),該函數(shù)接 受以下參數(shù):
(1)thisRef —— 必須與 屬性所有者 類(lèi)型(對(duì)于擴(kuò)展屬性——指被擴(kuò)展的類(lèi)型)相同或者 是它的超類(lèi)型
(2)property —— 必須是類(lèi)型 KProperty<*> 或其超類(lèi)型
這個(gè)函數(shù)必須返回與屬性相同的類(lèi)型(或其子類(lèi)型)。
2.對(duì)于一個(gè)可變屬性(即 var 聲明的),委托必須額外提供一個(gè)名為 setValue 的函數(shù),該函 數(shù)接受以下參數(shù):
(1)thisRef —— 同 getValue()
(2)property —— 同 getValue()
(3)new value —— 必須和屬性同類(lèi)型或者是它的超類(lèi)型

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

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

委托屬性的原理

在每個(gè)委托屬性的實(shí)現(xiàn)的背后,Kotlin 編譯器都會(huì)生成輔助屬性并委托給它。 例如,對(duì)于屬 性 prop ,生成隱藏屬性 prop$delegate ,而訪問(wèn)器的代碼只是簡(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 引用到外部類(lèi) C 的實(shí)例而 this::prop 是 KProperty 類(lèi)型的反射對(duì)象,該對(duì)象描述 prop 自身。

provideDelegate

通過(guò)定義 provideDelegate 操作符,可以擴(kuò)展創(chuàng)建屬性實(shí)現(xiàn)所委托對(duì)象的邏輯。 如果 by 右 側(cè)所使用的對(duì)象將 provideDelegate 定義為成員或擴(kuò)展函數(shù),那么會(huì)調(diào)用該函數(shù)來(lái) 創(chuàng)建屬性 委托實(shí)例。
provideDelegate 的一個(gè)可能的使用場(chǎng)景是在創(chuàng)建屬性時(shí)(而不僅在其 getter 或 setter 中) 檢查屬性一致性。
例如,如果要在綁定之前檢查屬性名稱(chēng),可以這樣寫(xiě):

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

在創(chuàng)建 MyUI實(shí)例期間,為每個(gè)屬性調(diào)用 provideDelegate方法,并立即執(zhí)行必要的驗(yàn)證。 如果沒(méi)有這種攔截屬性與其委托之間的綁定的能力,為了實(shí)現(xiàn)相同的功能, 你必須顯式傳遞 屬性名,這不是很方便:

//  檢查屬性名稱(chēng)而不使用“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 方法來(lái)初始化輔助的 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”來(lái)創(chuàng)建額外的“delegate”屬性
private val prop$delegate   =MyDelegate().provideDelegate(this,this::prop)
val prop:Type get() =
prop$delegate.getValue(this,    this::prop) 
}

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

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

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