kotlin類與對(duì)象——>委托、委托屬性
1.委托
1.1 委托的實(shí)現(xiàn)
委托模式已經(jīng)證明是實(shí)現(xiàn)繼承的一個(gè)很好的替代方式,而 Kotlin 可以零樣板代碼地原生支持它。 Derived 類可以通過(guò)將其所有公有成員都委托給指定對(duì)象來(lái)實(shí)現(xiàn)一個(gè)接口 Base :
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // by-子句表示 b 將會(huì)在 Derived 中內(nèi)部存儲(chǔ),并且編譯器將生成轉(zhuǎn)發(fā)給 b 的所有 Base 的方法。 class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived (b).print() }
1.2 覆蓋由委托實(shí)現(xiàn)的接口成員
覆蓋符合預(yù)期:編譯器會(huì)使用 override 覆蓋的實(shí)現(xiàn)而不是委托對(duì)象中的。如果將 override fun printMessage() { print("abc") } 添加到 Derived,那么當(dāng)調(diào)用 printMessage 時(shí)程序 會(huì)輸出“abc”而不是“10”:
interface Base { fun printMessage() fun printMessageLine() } class BaseImpl(val x: Int) : Base { override fun printMessage() { print(x) } override fun printMessageLine() { println(x) } } class Derived(b: Base) : Base by b { override fun printMessage() { print("abc") } } fun main() { val b = BaseImpl(10) Derived (b).printMessage() Derived (b).printMessageLine() }
請(qǐng)注意,以這種方式重寫的成員不會(huì)在委托對(duì)象的成員中調(diào)用 ,委托對(duì)象的成員只能訪問(wèn)其自身對(duì)接口成員實(shí)現(xiàn)
interface Base { val message: String fun print() } class BaseImpl(val x: Int) : Base { override val message = "BaseImpl: x = $x" override fun print() { println(message) } } class Derived(b: Base) : Base by b { // 在 b 的 `print` 實(shí)現(xiàn)中不會(huì)訪問(wèn)到這個(gè)屬性 override val message = "Message of Derived" } fun main() { val b = BaseImpl(10) val derived = Derived(b) derived.print() println (derived.message) }
注意:在 JVM 平臺(tái):當(dāng)使用帶有 default 方法的接口(包括帶有 @JvmDefault 注解的 Kotlin 接口)進(jìn)行委托時(shí),即使實(shí)際的委托類型提供了其自身的實(shí)現(xiàn)也會(huì)調(diào)用默認(rèn)實(shí)現(xiàn)。詳細(xì)信息請(qǐng)參 ?在 Java 中調(diào)用 Kotlin。
2.委托屬性
有一些常?的屬性類型,雖然我們可以在每次需要的時(shí)候手動(dòng)實(shí)現(xiàn)它們,但是如果能夠?yàn)榇蠹野阉麄冎?實(shí)現(xiàn)一次并放入一個(gè)庫(kù)會(huì)更好。例如包括:
— 延遲屬性(lazyproperties):其值只在首次訪問(wèn)時(shí)計(jì)算;
— 可觀察屬性(observableproperties):監(jiān)聽(tīng)器會(huì)收到有關(guān)此屬性變更的通知;
— 把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。
為了涵蓋這些(以及其他)情況,Kotlin 支持委托屬性:
class Example { var p: String by Delegate() }
語(yǔ)法是:val/var <屬性名>: <類型> by <表達(dá)式>。在by后面的表達(dá)式是該委托,因?yàn)閷傩詫?duì)應(yīng) 的 get()(與 set())會(huì)被委托給它的 getValue() 與 setValue() 方法。屬性的委托不必實(shí)現(xiàn) 任何的接口,但是需要提供一個(gè) getValue() 函數(shù)(與 setValue() ?對(duì)于 var 屬性)
例如:
import kotlin.reflect.KProperty 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.
自 Kotlin 1.1 起你可以在函數(shù)或代碼塊中聲明一個(gè)委托屬性,因此它不一定是類的成員
3.標(biāo)準(zhǔn)委托,Kotlin 標(biāo)準(zhǔn)庫(kù)為幾種有用的委托提供了工廠方法。
4.延遲屬性 Lazy
lazy() 是接受一個(gè) lambda 并返回一個(gè) Lazy <T> 實(shí)例的函數(shù),返回的實(shí)例可以作為實(shí)現(xiàn)延遲屬性 的委托:第一次調(diào)用 get() 會(huì)執(zhí)行已傳遞給 lazy() 的 lambda 表達(dá)式并記錄結(jié)果,后續(xù)調(diào)用get() 只是返回記錄的結(jié)果。
val lazyValue: String by lazy { println("computed!") "Hello" } fun main() { println(lazyValue) println (lazyValue)//只輸出Hello }
默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個(gè)線程中計(jì)算,并且所有 線程會(huì)看到相同的值。如果初始化委托的同步鎖不是必需的,這樣多個(gè)線程可以同時(shí)執(zhí)行,那么將
LazyThreadSafetyMode.PUBLICATION 作為參數(shù)傳遞給 lazy() 函數(shù)。而如果你確定初始化將 總是發(fā)生在與屬性使用位于相同的線程,那么可以使用 LazyThreadSafetyMode.NONE 模式:它不 會(huì)有任何線程安全的保證以及相關(guān)的開(kāi)銷。
5.可觀察屬性 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() { val user = User() user.name = "first" user.name = "second" } //如果你想截獲賦值并“否決”它們,那么使用 vetoable() 取代observable() 。在屬性被賦新值生 效之前會(huì)調(diào)用傳遞給 vetoable 的處理程序
6.把屬性儲(chǔ)存在映射中
一個(gè)常?的用例是在一個(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ò)字符串鍵?屬性的名稱):
println(user.name) // Prints "John Doe" println(user.age) // Prints 25
這也適用于 var 屬性,如果把只讀的 Map 換成 MutableMap 的話:
class MutableUser(val map: MutableMap<String, Any?>) { var name: String by map var age: Int by map }
7.局部委托屬性(自1.1起)
你可以將局部變量聲明為委托屬性。例如,你可以使一個(gè)局部變量惰性初始化
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() } }
//memoizedFoo變量只會(huì)在第一次訪問(wèn)時(shí)計(jì)算。如果someCondition失敗,那么該變量根本不會(huì)計(jì)算。
8.屬性委托要求
這里我們總結(jié)了委托對(duì)象的要求。
8.1 對(duì)于一個(gè)只讀屬性(即val聲明的),委托必須提供一個(gè)操作符函數(shù) getValue(),該函數(shù)具有以下參 數(shù)
//— thisRef ? 必須與 屬性所有者 類型(對(duì)于擴(kuò)展屬性?指被擴(kuò)展的類型)相同或者是其超類型。 //— property ? 必須是類型 KProperty<*> 或其超類型。 //getValue() 必須返回與屬性相同的類型(或其子類型)。 class Resource class Owner { val valResource: Resource by ResourceDelegate() } class ResourceDelegate { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return Resource() } }
8.2 對(duì)于一個(gè)可變屬性(即var聲明的),委托必須額外提供一個(gè)操作符函數(shù) setValue(),該函數(shù)具有以 下參數(shù):
//— thisRef ? 必須與 屬性所有者 類型(對(duì)于擴(kuò)展屬性?指被擴(kuò)展的類型)相同或者是其超類型。 //— property ? 必須是類型 KProperty<*> 或其超類型。 //— value — 必須與屬性類型相同(或者是其超類型)。 class Resource class Owner { var varResource: Resource by ResourceDelegate() } class ResourceDelegate(private var resource: Resource = Resource()) { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return resource } operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) { if (value is Resource) { resource = value } } }
getValue() 或/與 setValue() 函數(shù)可以通過(guò)委托類的成員函數(shù)提供或者由擴(kuò)展函數(shù)提供。當(dāng)你 需要委托屬性到原本未提供的這些函數(shù)的對(duì)象時(shí)后者會(huì)更便利。兩函數(shù)都需要用 operator 關(guān)鍵字來(lái)進(jìn)行標(biāo)記
8.3 委托類可以實(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) }
9.翻譯規(guī)則
在每個(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 引用到外部類 C 的實(shí) 例而 this::prop 是 KProperty 類型的反射對(duì)象,該對(duì)象描述 prop 自身。
請(qǐng)注意,直接在代碼中引用綁定的可調(diào)用引用的語(yǔ)法 this::prop 自 Kotlin 1.1 起才可用。
10. 提供委托(自1.1 起)
通過(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 中)檢測(cè)屬 性一致性。
例如,如果要在綁定之前檢測(cè)屬性名稱,可以這樣寫:
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> { override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... } } class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // 創(chuàng)建委托 return ResourceDelegate() } private fun checkProperty(thisRef: MyUI, name: String) { ...... } } class MyUI { fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ...... } 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)證
如果沒(méi)有這種攔截屬性與其委托之間的綁定的能力,為了實(shí)現(xiàn)相同的功能,你必須顯式傳遞屬性名,這不是很方便:
// 檢測(cè)屬性名稱而不使用“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) var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) //請(qǐng)注意,provideDelegate 方法只影響輔助屬性的創(chuàng)建,并不會(huì)影響為getter或setter生成的代碼。 }

浙公網(wǎng)安備 33010602011771號(hào)