源碼地址:https://github.com/cn-ljb/KotlinBlogs
委託
一、委託類
什麼是委託類?
代理設計模式,在Java中實現一個簡單的代理模式如下:
//抽象功能
public interface Base {
void doSome();
}
//實際操作類
public class BaseImpl implements Base {
@Override
public void doSome() {
System.out.println("java do some thing.");
}
}
//代理類
public class BaseProxy implements Base {
private Base impl;
public BaseProxy(Base impl) {
this.impl = impl;
}
@Override
public void doSome() {
impl.doSome();
}
}
Kotlin在語法上提供by關鍵字,所以實現以上代碼,可以更簡潔點:
interface Base {
fun doSome()
}
class BaseImpl : Base {
override fun doSome() {
println("do some thing...")
}
}
class BaseProxy(val b: Base) : Base by b
當然,你可以在代理中擴展doSome函數,只需覆寫它即可
class BaseProxy(val b: Base) : Base by b{
override fun doSome() {
println("pre")
b.doSome()
println("next")
}
}
二、委託屬性
委託類並不是重點,主要看看委託屬性。
什麼是委託屬性?
就如同代理設計模式一樣 在調用屬性或者賦值屬性值時,我們希望做些額外操作,Kotlin基本語法如下:
val/var <property name>: <Type> by <expression>
by 後面的表達式就是代理,因爲get() set() 對應的屬性會被 getValue() setValue() 方法代理。屬性代理不需要任何接口的實現,但必須要提供 getValues() 函數(如果是 var 還需要 setValue())。
例如:
class Person() {
var name: String by PrefixName()
constructor(name: String) : this() {
this.name = name
}
}
class PrefixName {
private var proxyName: String = "admin_"
operator fun getValue(person: Person, property: KProperty<*>): String {
return "admin_$proxyName"
}
operator fun setValue(person: Person, property: KProperty<*>, s: String) {
proxyName = s
}
}
我們將Person的name屬性委託給了PrefixName類,該類提供了該屬性新的getValue()和setValue()函數,將來在使用Person的name屬性時,實際訪問的是該類中的代理。
//代理屬性
val per = Person("Jake")
println(per.name)
per.name = "haha"
println(per.name)
輸出:
admin_Jake
admin_haha
很多同學可能會納悶,輸出結果中可以看出一直使用的是PrefixName中的返回的屬性,那Person中定義的name成員有什麼意義,不能直接操作嗎?
其實,Person中是沒有name屬性的,定義語句只是Kotlin中的語法格式罷了。不信?我們反編譯字節碼,看看Java代碼中到底有些什麼東西。
反編譯爲Java代碼:
雖然Java代碼裏存在setter和getter方法,但是並沒有name屬性。也就是說我們的猜想是沒有錯的,Person中的name屬性被代理後,實際Person類中並不存在該屬性,從而替代它的是代理對象。
無論是Person中的setter還是getter,實際調用的都是代理對象中setValue,getValue ,不妨看下反編譯後的代理屬性類是什麼樣子的:
發現除了我們自己寫的代碼外,只多了幾行檢查參數的代碼,也就是說Kotlin強制要求代理屬性裏的參setValue()\getValue()參數數必須不爲null,如果參數爲null,則拋出異常。
注意:被定義爲代理的屬性,該類中實際是不存在的
理解這句話,不要被Kotlin的語法所迷惑。
標準委託屬性
我們除了可以自定義委託屬性外,Kotlin也爲我們提供了集中常用的委託屬性。
1、延遲屬性
lazy()
該委託可以保證屬性在使用是才被初始化出來
例如:
val str: String by lazy { "abc" }
定義時,在lazy後面的Lambda表達式進行初始化操作即可。細心的朋友也已經注意到lazy其實就是一個接收函數的函數(可以直接傳遞Lambda表達式)。
貌似有點不對啊,委託屬性by關鍵字之後寫的對象類,不是應該提供setValue()\getValue()函數嗎?
那麼我們不妨繼續看下lazy()函數,發現其返回的是Lazy的一個對象。
然而Lazy是個接口,那麼實際SynchronizedLazyImpl應該是Lazy中的子類對象,那麼這個子類對象有setValue()\getValue()函數嗎
我們發現子類對象,也沒有這兩個函數,這不科學啊,不滿足定義一個委託屬性的規則啊。先別急,別忘了,Kotlin中有個神奇的特性:擴展函數。
原來Lazy的getValue()是通過擴展函數實現的,那麼setValue()呢?很遺憾,並沒有,也就是說lazy委託只能給只讀屬性(val)使用:使用時初始化,初始化之後不能再進行賦值(當然你可以手動給Lazy添加一個擴展setValue()函數,但是如果這樣便曲解了lazy委託的意義)。
既然這樣,我們不妨在看看lazy是怎麼實現的,其實讓我們自己實現也完全沒問題吧。
繼續看實際的Lazy實現類 SynchronizedLazyImpl類, 注意裏面的value屬性的getter()函數,擴展的getValue()函數返回的就是這個屬性。
使用起來也跟普通的屬性沒有任何區別
//...沒使用前是不會被初始化的
println(str) //輸出:abc
如果該屬性需要多線程訪問,爲了保證線程安全,lazy()函數提供了兩個參數的重載函數:
//多線程安全
val str2: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "bcd" }
2、可觀察屬性
Delegates.observable()
有點類似於觀察者設計模式,當屬性值發生改變時,發出事件並做出響應事件。
怎麼用呢?
class Person() {
var name: String by PrefixName()
var age: Int by Delegates.observable(0, {
property, oldValue, newValue ->
println("屬性名:${property.name} , 舊值:$oldValue , 新值:$newValue")
})
constructor(name: String) : this() {
this.name = name
}
}
我們爲Person類添加一個age屬性,該屬性使用了可觀察委託,第一個參數是屬性初始值,一但屬性值發生改變,第二個參數裏的Lambda(函數)就會被調用。
調用:
per.age = 10
輸出:
屬性名:age , 舊值:0 , 新值:10
那麼底層的實現代碼過程就不帶大家看了,套路是一樣,by之後的對象類提供setValue()\getValue()函數,貼出主要部分,具體的實現感興趣的同學按照這個套路看源碼即可,不難~
3、Map存儲屬性
map
該委託用於將map中存儲的屬性讀取到Mode中,Kotlin官方考慮到如果存在一個Map集合,並且該集合的鍵值對對應Mode中的屬性,那麼可以使用該委託(實用性有待考察)。
這個就不講了,個人感覺有點雞肋,Json解析有各種解析工具(Gson、fastjson等),可能是個人使用場景使用不到吧,上一個官方demo結束。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
在這個例子中,構造函數接受一個 map :
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
var 屬性可以用 MutableMap 代替只讀的 Map:
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
總結
除了瞭解常用的委託外,最主要的還是理解一個委託它究竟做了什麼,怎麼實現的?哪裏不明白,反編譯看Java源碼可能會更直接,不得不承認Kotlin的語法糖太多,有些地方真的閱讀起來比較晦澀,不過沒事,誰讓咱們懂Java。