Kotlin 的類委託及委託屬性詳解
類委託 | 屬性委託 |
---|---|
by | 延遲屬性(lazy) |
- | 可觀測屬性(Observable) |
- | 非空屬性(Delegate.notnull) |
- | map屬性(Map<String, Any?>) |
- | 提供委託(provideDelegate ) |
類委託
class MyDelegation{
operator fun getValue(thisRef:Any?,protery:KProperty<*>):String="$thisRef,your delegated protery name is ${protery.name}"
operator fun setValue(thisRef:Any?,protery:KProperty<*>,value:String)= println("$thisRef,new value is $value")
}
class MyProteryClass{
var name:String by MyDelegation() //屬性申明必須初始化,這裏把當前屬性委託給了MyDelegation類。
}
fun main(args: Array<String>) {
val myproteryClass=MyProteryClass()
myproteryClass.name="Hello world" //這是設置value
println(myproteryClass.name) //這是獲取value
}
類委託原理:by關鍵字後面的對象實際上被儲存在類的內部,編譯器則會父接口中的所有方法實現出來,並且將實現轉移給委託對象去執行。 by後面的就是委託給當前對象。
屬性委託
延遲屬性(lazy):指的是屬性只在第一次被訪問的時候纔會計算,之後則會將之前計算的結果緩存起來供後續調用。
lazy後有三種情況:
- SYNCHRONIZED: 默認情況下延遲屬性是同步的,值只會在一個線程中得到計算,所有線程都會使用相同的結果。
- PUBLICATION:如果不需要初始化委託的同步,這樣多個線程可以同時執行。
- NONE:如果確定初始化操作只在一個線程中執行,這樣會減少線程安全方面的開銷。
val myLazyValue:Int by lazy(LazyThreadSafetyMode.NONE){
println("hello world")
30
}
fun main(args: Array<String>) {
println(myLazyValue)
}
非空屬性(notNull):notNull 適用於那些在初始化無法確定屬性值的場合。
class MyPerson{
var address:String by Delegates.notNull<String>() //notNull
}
fun main(args: Array<String>) {
val myPerson= MyPerson()
myPerson.address="chongqing"
println(myPerson.address)
}
可觀測屬性(Observable):
Delegates.Observable 接收2個參數:初始值與修改處理器。處理器會在我們每次賦值時得到調用(在賦值完成後被調用)。處理器本身接收3個參數:被賦值的屬性本身,舊的屬性值與新的屬性值。
Delegates.vetoable 調用時機與Delegates.Observable相反,它是在對屬性賦值之前被調用,根據Delegates.vetoable返回的 結果是true或false來決定是否真正對屬性賦值。
class Person{
var age:Int by Delegates.Observable(20){
prop,oldvalue,newvalue->println("${prop.name},oldvalue:$oldvalue,newvalue:$newvalue")
}
}
class Person2{
var age:Int by Delegates.vetoable(20){
prop,oldvalue,newvalue->when{
oldvalue<=newvalue->true
else ->false
}
}
}
fun main(args: Array<String>) {
val myPerson= Person()
myPerson.age=30 //得到結果:age,oldvalue:20,newvalue:30
myPerson.age=40 //得到結果:age,oldvalue:30,newvalue:40
println("----------")
val myPerson2= Person2()
println(myPerson2.age) //得到結果:20
myPerson2.age=40
println(myPerson2.age) //得到結果:40,因爲20<40得到true,賦值成功。
println("----------")
myPerson2.age=30
println(myPerson2.age) //因爲40>30得到false,賦值失敗。新值是40了。
}
map屬性(Map):
將屬性儲存到map中,一種常見的應用場景是將屬性值儲存到map中。這通常出現在json解析或是一種動態行爲。在這種情況中,你可以將map實例作爲委託,作爲類中的屬性的委託。map中的key名字要與類中屬性名字一致。
class Student(map: Map<String, Any?>) {
val name: String by map
val address: String by map
val age: Int by map
val birthday: Date by map
}
class Student2(map: MutableMap<String, Any?>) { //可變的map
val address: String by map
}
fun main(args: Array<String>) {
val student = Student(
mapof(
"name" to "zhangsan",
"address" to "chengdu", //必須用to
"age" to20,
"birthday" to Date()
)
)
println(student.name)
println(student.address)
println(student.age)
println(student.birthday)
val map=MutableMapOf("address" to "chongqing")
val student2 = Student2(map) //此時把map放進來會報Reuqerd:String,Any? found:String,String
//修改上面報錯的問題,定義返回的類型
val map2:MutableMap<String, Any?>=MutableMapOf("address" to "chongqing")
val student2 = Student2(map2) 就不會報錯了
println(map2["address"]) //chongqing
println(student2.address)//chongqing
println("------------")
student2.address="chengdu"
println(map2["address"])//chengdu
println(student2.address)//chengdu
}
提供委託(providing a delegate)
通過定義provideDelegate operator,我們可以擴展委託的創建邏輯過程。如果對象定義了provideDelegate方法,那麼該方法就會被調用來創建屬性委託實例。
class PropertyDelegate:ReadOnlyProperty<People,String>{
override fun getValue(thisRef:People,property:KProperty<*>):String{
return "hello world"
}
}
class PeopleLauncher{
operator fun provideDelegate(thisRef:People,property:KProperty<*>):ReadOnlyProperty<People,String>{
when(property.name){
"name","address"->return PropertyDelegate()
else ->throw Exception("not valid name")
}
}
}
class People{
val name:String by PeopleLauncher()
val address:String by PeopleLauncher()
}
fun main(args: Array<String>) {
val people=People()
println(people.name) //輸出 hello world
println(people.address) //輸出 hello world
println(people.age) // "not valid name" 因爲屬性名稱不一致
}
屬性委託的要求
對於只讀屬性來說(val修飾的屬性),委託需要提供一個名爲getvalue的方法,該方法接收如下參數:
-thisRef: 需要是屬性擁有者相同的類型或是父類型(對於擴展屬性來說,這個類型指的是被擴展的那個類型)
-property: 需要時KProperty<*>類型或是其父類型
getValue方法需要返回與屬性相同的類型或是其子類型
對於可變屬性來說(var修飾的屬性),委託需要再提高一個setvalue方法,該方法需要接收如下參數:
-thisRef: 與getvalue方法thisRef要求一致。
-property: 與getvalue方法property要求一致。
-new value:需要與屬性的類型相同或是其父類型。
getValue與setvalue方法既可以作爲委託類的成員方法實現,也可以作爲其擴展方法來實現。
這2個方法都必須要標記爲operator關鍵字,對於委託類來說,它可以實現ReadOnlyProperty或是ReadWriteProperty接口,
這些接口包含了相應的getvalue與setvalue方法,對於委託類來說,也可以不去實現這2個接口,而是自己單獨實現相應的
getvalue與setvalue方法。
委託轉換規則:
對於每個委託屬性來說,Kotlin編譯器在底層會生成一個輔助的屬性,然後將對原有屬性的訪問委託給這個輔助屬性。比如
說,對於屬性prop來說,Kotlin編譯器所生成的隱含的屬性名爲prop$delegate的屬性,然後對原有的prop屬性的
訪問器的訪問都只是委託給了這個額外的,Kotlin編譯器所生成的輔助屬性。