委托屬性的語法
val/var <屬性名>:<類型> by <表達式>
在by后面的表達式是改委托抓半,因為屬性對應的get()(與set())會被委托給它的getValue()與setValue()方法扔字。屬性的委托不必實現(xiàn)任何接口囊嘉,但需要提供一個getValue()函數(shù)(var屬性還需要提供setValue()函數(shù))
例如:
fun main(){
val e=Example()
println(e.d)
e.d=6
}
class Example{
var d:Int by DelegateTest()
}
class DelegateTest{
operator fun getValue(thisRef:Any?,property:KProperty<*>):Int{
return 1
}
operator fun setValue(thisRef: Any?,property: KProperty<*>,value:Int){
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
輸出的結(jié)果:
1
6 has been assigned to 'd' in com.hx.learn.Example@433c675d.
從輸出的結(jié)果可以知道,當我們從讀取實例的d的值的時候篷角,會調(diào)用委托類的getValue()函數(shù)焊刹,當我們給實例的d賦值的時候,會調(diào)用setValue()函數(shù)恳蹲。
從上面委托類的兩個函數(shù)中看到都包含了thisRef
和property
兩個參數(shù)的虐块,第一個參數(shù)是讀出d的對象,第二個參數(shù)保存了對d自身的描述嘉蕾。
屬性委托的要求
- 對于一個只讀屬性(即val聲明的)贺奠,委托必須提供一個操作函數(shù)getValue(),getValue()的函數(shù)的返回值必須與屬性相同類型(或者是其值類型)错忱,并且函數(shù)具有以下參數(shù)
thisRef
必須與屬性所有者類型相同或者是其超類儡率。property
必須是類型KProperty<*>
或者是其超類。
- 對于一個可變屬性(即var聲明的)以清,委托必須額外提供一個操作函數(shù)setValue()儿普,該函數(shù)具有以下參數(shù):
thisRef
必須與屬性所有者類型相同或者是其超類。property
必須是類型KProperty<*>
或者是其超類掷倔。value
必須與屬性類型相同(或者是其超類)
標準委托
Kotlin標準庫為幾種有用的委托提供了工廠方法:
延遲屬性Lazy
lazy()
是接受一個lambda并返回一個Lazy<T>實例的函數(shù)眉孩,返回的實例可以延遲屬性的委托:第一次調(diào)用get()會執(zhí)行已傳遞給lazy()的lamdba表達達式并記錄結(jié)果,后續(xù)調(diào)用get()只返回記錄的結(jié)果勒葱。
fun main(){
val lazyValue:String by lazy{
println("你好")
"lazy"
}
println(lazyValue)
println("-----------")
println(lazyValue)
}
在默認的情況下浪汪,對于lazy屬性的求值是同步鎖的:該值只在前一個線程中計算,并且所有線程看到相同的值凛虽。如果初始化委托的同步鎖不是必需的死遭,這樣多個線程可以同時執(zhí)行,那么將
LazyThreadSafetyMode.PUBLICATION
作為參數(shù)傳遞給lazy()函數(shù)凯旋。而如果你確定初始化將總是發(fā)生在與屬性使用位于相同的線程呀潭,那么可以使用LazyThreadSafetyMode.NONE
作為Lazy()函數(shù)的參數(shù)钉迷,它不會有任何線程安全的保證以及 相關(guān)的開銷。
可觀察屬性O(shè)bservable
Delegates.observable()
接受兩個參數(shù):初始化與修改時處理程序蜗侈。每當我們給屬性賦值時會調(diào)用該處理程序(在賦值后執(zhí)行)篷牌。它有是三個參數(shù):被賦值的屬性,舊值與新值踏幻。
例如:
fun main(){
var person=Person()
person.age=15
person.age=16
}
class Person{
var age:Int by Delegates.observable(10){
property, oldValue, newValue ->
println("${property.name}:{$oldValue}->{$newValue}")
}
}
結(jié)果:
age:{10}->{15}
age:{15}->{16}
observable()是在賦值之后調(diào)用處理程序的,如果想在屬性被賦新值生效前會調(diào)用處理程序可以使用vetoable()戳杀。
voteable的處理程序中返回的是布爾值该面,如果返回為ture則讓賦值生效,否則賦值不生效信卡。
委托給另一個屬性
從kotlin 1.4開始隔缀,一個屬性可以幫它的getter與setter委托給另一個屬性。該委托屬性可以是:
- 頂層屬性
- 同一個類的成員或擴展屬性
- 另一個類的成員或擴展屬性
將一個屬性委托給另一個屬性傍菇,應在委托名稱中使用::
限定符猾瘸。當想要以一種向后兼容的方式命名一個屬性性時,使用@Deprecated注解來注解舊的屬性,并委托其實現(xiàn)丢习。
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
myClass.oldName = 42
println(myClass.newName) // 輸出42
}
將屬性儲存在映射中
常見的用例就是在一個映射(map)里面存儲的值牵触,可以使用映射實例自身作為委托來實現(xiàn)委托屬性。
例如:
fun main(){
var person=Person(
mapOf("name" to "Kotlin"
,"age" to 10)
)
}
class Person(map:Map<String,Any?>){
val age:Int by map
val name:String by map
}
委托屬性會從這個映射中取值(通過字符串鍵——屬性的名稱)
println(person.age)
println(person.name)
輸出:
10
Kotlin
如果是var屬性的咐低,需要將只讀的Map換成MutableMap揽思。
局部委托屬性
fun main(){
example(2)
}
fun example(value:Int){
val tempInt by lazy {
println("第一次被調(diào)用")
value
}
println(tempInt)
println(tempInt)
}
結(jié)果:
第一次被調(diào)用
2
2
tempInt變量只有在第一才訪問時,才會執(zhí)行處理程序,之后訪問就不再執(zhí)行见擦。