代理屬性 Delegated Properties
一些特定的常見類型的屬性, 盡管我們可以在每次需要的時候實現他們, 但是如果我們一次把他們全部實現并放在一個庫中, 這會非常方便, 包括:
延遲屬性: 只在第一次訪問的時候計算值
廣播屬性: 當屬性的值改變時通知觀察者
將數據存儲在鍵值對中, 而不是獨立的域中.
Kotlin提供的代理屬性, 包含了這些(以及其他)例子:
class Example{
var p: String by Delegate()
}
語法是:val/var
屬性代理不需要實現任何接口, 但他們需要提供一個getValue()方法(對于var---還需要提供setValue()).
例如:
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.'")
}
}
當我們從被代理給Delegate實例的p時,Delegate的getValue()方法被調用,
第一個參數是讀取p所在的對象, 第二個參數保存p自身的描述(例如: 你可以獲取它的名字). 例如:
val e = Example()
println(e.p)
打印結果:
Example@33a17727, thank you for delegating ‘p’ to me!
類似的, 當我們給p賦值時,setValue()方法被調用. 前兩個參數是相同的, 第三個參數保存被賦的新值:
e.p = "NEW"
打印結果:
NEW has been assigned to ‘p’ in Example@33a17727.
關于代理對象的需求的說明可以在[這里]找到(delegated-properties.html#property-delegate-requirements).
需要注意的是從Kotlin 1.1之前你可以在方法或代碼塊中聲明代理屬性了, 代理屬性不必聲明為類的成員,例子.
標準庫中的代理 Standard Delegates
Kotlin標準庫為一些常用的代理提供了工廠方法.
延遲屬性 Lazy
lazy()方法接收一個lamda作為參數并返回一個Lazy
第一次調用get()時執行傳入lazy()的lambda表達式并保存結果, 后續對get()的調用只返回保存的結果.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array
println(lazyValue)
println(lazyValue)
}
該例子輸出:
computed!
Hello
Hello
同步的
默認情況下, 延遲屬性的計算是(synchronized): 只有一個線程計算該值, 其他的線程都會看見相同的值. 如果此初始化的步驟不需要同步, 多個線程可以同事執行初始化, 在lazy()方法中傳入LazyThreadSafetyMode.PUBLICATION作為參數.
如果可以確保初始化過程只會在單個線程中執行, 可以用LazyThreadSafetyMode.NONE模式, 該模式不保證線程安全, 避免相關的開銷.
監控屬性 Observable
Delegates.observable()有兩個參數: 初始值和變化觀察器.
每次代理屬性被賦予值的時候都會調用觀察器(在賦值操作之后).觀察器有三個參數:屬性類型, 舊值和新值.
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array
val user = User()
user.name = "first"
user.name = "second"
}
該例子輸出
first -> second
如果你想終端賦值的過程并拒絕賦值, 用vetoable()替代observable().
observable()的觀察器參數是在賦值之前被調用的.
使用Mapc存儲屬性 Storing Properties in a Map
在map中存儲屬性是一種常見使用方式.
這種情形在解析JSON或者其他"動態的"事情時經常出現.
在這種情況下, 你可以使用map的實例來代理一個代理屬性.
classUser(val map: Map
val name: String by map
val age: Int by map
}
在這個例子中, 構造器接收一個map:
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
代理屬性從這個map接收值(通過String類型的key --- 作為屬性的名字)
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
當使用MutableMap而不是只讀的Map時, 對var也可以使用.
classMutableUser(val map: MutableMap
var name: String by map
var age: Int by map
}
本地代理屬性 Local Delegated Properties (since 1.1)
你可以聲明局部變量作為代理屬性.
例如, 可以讓局部變量成為lazy屬性.
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo變量只會在第一次訪問時計算.
如果someCondition失敗了, 變量的值則完全不會進行計算.
屬性代理的需求 Property Delegate Requirements
在此我們整理一下代理對象的需求.
只讀
對于一個屬性(例如:val), 代理必須提供一個接收下列參數的getValue函數:
thisRef--- 必須是_屬性擁有者_相同類型或者是其超類(對于擴展屬性 --- 則是其所擴展的屬性)
property--- 必須是KProperty<*>類型或其超類,
這個函數必須返回和屬性相同的類型, 或者其子類.
可變
對于的屬性(比如var), 代理必須額外提供具備下列參數的setValue函數:
thisRef--- 與getValue()相同,
property--- 與getValue()相同,
new value --- 必須是與屬性或其超類相同的類型
getValue()和/或setValue()函數可以用兩種方式提供: 代理類的成員函數或者擴展函數.
后者在原有對象沒有提供這些函數時非常方便.兩種方式的函數都要使用operator關鍵字修飾.
代理類可以實現下面的接口之一, 包含operator方法的ReadOnlyProperty和ReadWriteProperty接口. 這些接口在Kotlin標準庫中聲明.
interface ReadOnlyProperty
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
翻譯規則 Translation Rules
在代理屬性的背后, Kotlin編譯器生成一個輔助屬性并代理給它. 比如, 對于屬性prop, 會生成一個prop$delegate輔助屬性, 訪問器的代碼就是簡單的代理給這個附加的屬性:
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
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編譯器提供了所有prop屬性必需的信息: 第一個參數this引用指向包含它的外部類C,this::prop是prop自身的反射類型信息, 是KProperty類型.
提供代理 Providing a delegate (since 1.1)
通過定義provideDelegate操作符可以擴展創建屬性實現所代理對象的邏輯.如果by右側使用的對象定義了provideDelegate作為成員函數或者擴展函數, 這個函數會在創建屬性代理時被調用.
One of the possible use cases ofprovideDelegateis to check property consistency when the property is created, not only in its getter or setter.
provideDelegate一個可能的用法是用來在創建屬性期間檢查屬性的一致性, 而不是在getter或setter中.
比如你想在綁定前檢查屬性的名字, 可以這樣寫:
class ResourceLoader
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>
): ReadOnlyProperty
checkProperty(thisRef, prop.name)
// 創建代理
}
private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
fun
class MyUI {
val image bybindResource(ResourceID.image_id)
val text bybindResource(ResourceID.text_id)
}
provideDelegate和getValue的參數相同:
thisRef--- 必須與屬性的擁有者或其超類類型相同(對于擴展屬性 -- 指被擴展的類)
property--- 必須是KProperty<*>類型或其超類.
TheprovideDelegatemethod is called for each property during the creation of theMyUIinstance, and it performs the necessary validation right away.
provideDelegate方法在每個MyUI實例創建期間都被調用, 并立即進行必要的檢驗.
如果沒有這種在屬性和其代理之間攔截的手段, 要明確的傳入屬性命, 這很不方便.
// 沒有"provideDelegate"的情況下檢查屬性命
class MyUI {
val image by bindResource(ResourceID.image_id, "image")
val text by bindResource(ResourceID.text_id, "text")
}
fun
id: ResourceID
propertyName: String
): ReadOnlyProperty
checkProperty(this, propertyName)
// 創建委托
}
在生成的代碼中,provideDelegate被調用, 以此來初始化輔助prop$delegate屬性. 比較聲明是val prop: Type by MyDelegate()的屬性生成的代碼和上面不提供provide Delegate函數的代碼.
class C {
var prop: Type by MyDelegate()
}
// 在提供`provideDelegate`函數時, 這些代碼由編譯器生成
class C {
// 調用"provideDelegate"函數創建額外的"delegate"屬性
private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
val prop: Type
get() = prop$delegate.getValue(this, this::prop)
}
注意provideDelegate函數只影響輔助屬性的創建, 并不影響getter和setter的生成.
本文為個人翻譯的Kotlin官方文檔, 原文連接:Delegated Properties
新聞熱點
疑難解答