前序
??????委托琴许,對于很多Java開發(fā)者來說都會一面蒙蔽,我也不例外溉躲。委托榜田,維基百科的解釋是:有兩個對象參與處理同一個請求,接受請求的對象將請求委托給另一個對象來處理签财。這好像有一點代理的味道(*゜ー゜*)串慰。Kotlin中委托分為類委托和委托屬性。
類委托
??????在解釋類委托之前唱蒸,需要先了解一波裝飾設計模式邦鲫。裝飾設計模式的核心思想是:
在不使用繼承的情況下,擴展一個對象的功能神汹,使該對象變得更加強大庆捺。
??????通常套路是:創(chuàng)建一個新類,新類實現(xiàn)與原始類一樣的接口屁魏,并將原來的類的實例作為作為一個字段保存滔以,與原始類擁有同樣的行為(方法)。一部分行為(方法)與原始類保持一致(即直接調(diào)用原始類的行為(方法))氓拼,還有一部分行為(方法)在原始類的行為(方法)基礎上進行擴展你画。
??????裝飾設計模式的缺點是需要較多的樣板代碼,顯得比較啰嗦桃漾。例如:最原始的裝飾類需要實現(xiàn)接口的全部方法坏匪,并在這些方法中調(diào)用原始類對象對應的方法。
class CustomList<T>(
val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> {
override val size: Int = innerList.size
override fun contains(element: T): Boolean = innerList.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
override fun isEmpty(): Boolean = innerList.isEmpty()
override fun add(element: T): Boolean = innerList.add(element)
override fun addAll(elements: Collection<T>): Boolean = innerList.addAll(elements)
override fun clear() = innerList.clear()
override fun iterator(): MutableIterator<T> = innerList.iterator()
override fun remove(element: T): Boolean = innerList.remove(element)
override fun removeAll(elements: Collection<T>): Boolean = innerList.removeAll(elements)
override fun retainAll(elements: Collection<T>): Boolean = innerList.retainAll(elements)
}
??????但Kotlin將委托作為一個語言級別的功能進行頭等支持撬统∈首遥可以利用by
關鍵字,將新類的接口實現(xiàn)委托給原始類恋追,編譯器會為新類自動生成接口方法凭迹,并默認返回原始類對應的具體實現(xiàn)罚屋。然后我們重載需要擴展的方法。
class CustomList<T>(
val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> by innerList{
override fun add(element: T): Boolean {
println("CustomList add element")
innerList.add(element)
}
}
委托屬性
委托屬性就是將屬性的訪問器(
get
和set
)委托給一個符合屬性委托約定規(guī)則的對象的嗅绸。
??????委托屬性和類委托不同脾猛,委托屬性更像是給屬性找代理。 委托屬性同樣是利用by
關鍵字鱼鸠,將屬性委托給代理對象尖滚。屬性的代理對象不必實現(xiàn)任何的接口,但是需要提供一個 getValue()
函數(shù)與 setValue()
函數(shù)(僅限 var
屬性)瞧柔。例如:
class Person{
var name:String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "kotlin"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
}
}
??????屬性 name
將自己的set
/get
方法委托給了Delegate對象的getValue()
和 setValue()
漆弄。在getValue()
和 setValue()
中都有operator
修飾,意味著委托屬性也是依賴于約定的功能造锅。像其他約定的函數(shù)一樣撼唾,getValue()
和 setValue()
可以是成員函數(shù),也可以是擴展函數(shù)哥蔚。
??????Kotlin官方庫中提供 ReadOnlyProperty
或 ReadWriteProperty
接口倒谷,方便開發(fā)者實現(xiàn)這些接口來提供正確的 getValue()
方法 和 setValue()
方法。
public interface ReadOnlyProperty<in R, out T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
}
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
使用委托屬性
惰性初始化
??????當需要進行屬性延遲初始化時糙箍,往往會想到使用lateinit var
進行延遲初始化渤愁。但那是對于var
變量,即可變變量深夯,但對于val
變量呢抖格?可以使用支持屬性來實現(xiàn)惰性初始化:
class Person{
//真正存儲郵箱列表的對象
private var _emails:List<Email>? = null
//對外暴露的郵箱列表對象
val emails:List<Email>
get() {
if ( _emails == null){
_emails = ArrayList<Email>()
}
return _emails!!
}
}
??????提供一個"隱藏"屬性_emails
用來存儲真正的值,而另一個屬性emails
用來提供屬性的讀取訪問咕晋。_emails
是可變可空雹拄,emails
不可變不可空,當你訪問emails
時掌呜,才初始化_emails
變量滓玖,并返回_emails
對象,達到對val
對象延遲初始化的目的质蕉。
??????但這種方案在需要多個惰性屬性時势篡,就顯得很啰嗦了,而且他并不是線程安全的模暗。Kotlin提供了更加便捷的解決方案:委托屬性禁悠,并使用標準庫函數(shù)lazy
返回代理對象。
class Person{
val email:List<Email> by lazy {
ArrayList<Email>()
}
}
??????lazy
函數(shù)接收初始化該值操作的lambda汰蓉,并返回一個具有getValue()
方法的代理對象绷蹲,并配合by
關鍵字將屬性委托給lazy
函數(shù)返回的代理對象棒卷。lazy
函數(shù)是線程安全的顾孽,不用擔心異步的問題祝钢。
屬性改變的通知
??????當一個對象的屬性需要更改時得到通知,最原始的辦法就是若厚,重寫set方法拦英,在set方法中設置處理屬性改變的邏輯。手工實現(xiàn)屬性修改的通知:
class Person(name:String,age:Int){
var age :Int = age
set(newValue) {
val oldValue = field
field = newValue
//監(jiān)聽值改變(或使用Listener對象)
valueChangeListener("age",oldValue,newValue)
}
var name :String = name
set(newValue) {
val oldValue = field
field = newValue
//監(jiān)聽值改變
valueChangeListener("name",oldValue,newValue)
}
fun <T> valueChangeListener(fieldName:String,oldValue:T,newValue:T){
println("$fieldName oldValue = $oldValue newValue = $newValue")
}
}
??????但這種方案在跟惰性初始化最開始的例子類似测秸,當需要監(jiān)聽多個屬性時疤估,代碼冗長且啰嗦。我們可以像惰性初始化一樣霎冯,使用委托屬性實現(xiàn):
class Person(name:String,age:Int){
var age:Int by PropertyObservable(age){ property, oldValue, newValue ->
}
var name:String by PropertyObservable(name){ property, oldValue, newValue ->
}
}
//委托類
class PropertyObservable<T>(var initValue:T,
val observer:(property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Person, T> {
override fun getValue(thisRef: Person, property: KProperty<*>): T {
return initValue;
}
override fun setValue(thisRef: Person, property: KProperty<*>, newValue: T) {
val oldeValue = initValue
initValue = newValue
//監(jiān)聽值改變(或使用Listener對象)
observer(property,oldeValue,newValue)
}
}
??????定義委托類铃拇,通過委托屬性"接管"該屬性的get
/set
,并提供初始值沈撞,以及屬性被修改時的處理邏輯慷荔。大大簡化屬性設置監(jiān)聽的代碼。
??????但Kotlin在標準庫中已經(jīng)為我們提供了Delegates.observable()
方法,大大方便我們使用委托屬性對屬性的修改進行監(jiān)聽缠俺,像我們自定義的委托類一樣显晶,該方法接受屬性的初始化值,以及屬性變化時的處理邏輯:
class Person(name:String,age:Int){
var age:Int by Delegates.observable(age){ property, oldValue, newValue ->
println("${property.name} oldValue = $oldValue newValue = $newValue")
}
var name:String by Delegates.observable(name){ property, oldValue, newValue ->
println("${property.name} oldValue = $oldValue newValue = $newValue")
}
}
??????Kotlin在標準庫中提供了一個類似Delegates.observable()
的方法:Delegates.vetoable()
壹士。但會在屬性被賦新值生效之前會傳遞給 Delegates.vetoable()
進行處理磷雇,依據(jù)Delegates.vetoable()
的返回的布爾值判斷要不要賦新值。
第三種延遲初始化
??????之前已經(jīng)知道躏救,var
屬性需要延遲初始化時唯笙,可以使用lateinit
關鍵字,val
屬性需要延遲初始化時盒使,可以使用委托屬性 + lazy()
函數(shù)的方法。但lateinit
關鍵字的延遲處理僅對引用類型有用呢堰,對基本數(shù)據(jù)類型無效枉疼,當需要對基本數(shù)據(jù)類型進行延遲初始化怎么辦呢鞋拟?Kotlin通過委托屬性提供另一種延遲初始化的方式:Delegates.notNull()
var num:Int by Delegates.notNull()
??????雖然Kotlin提供了延遲初始化的方式航闺,使開發(fā)者不用強制在構造函數(shù)中初始化(例如Activity
中在onCreate
中初始化),但對于延遲初始化的值侮措,必須確保其被初始化分扎,否則將會像Java空指針一樣畏吓,拋出異常菲饼。
方式 | 適用類型 |
---|---|
lateinit | 引用類型 |
Delegates.notNull() | 基本數(shù)據(jù)類型巴粪、引用類型 |
轉(zhuǎn)換規(guī)則
??????每個委托屬性的實現(xiàn)的背后肛根,Kotlin 編譯器都會生成輔助屬性并委托給它派哲。例如:對于屬性 name
,編譯器會生成隱藏屬性 name$delegate感耙,而屬性 name
訪問器的代碼委托給隱藏屬性的getValue()/setValue()即硼。
class Person{
var name:String by MyDelegate()
}
編譯器生成以下代碼:
class Person{
private val name$delegate = MyDelegate()
var name:String
get() = name$delegate.getValue(this,<property>)
set(value:String) = name$delegate.setValue(this,<property>,value)
}
- thisRef表示持有該委托屬性的對象
- property KProperty<*> 類型或是它的父類褥实,屬性的描述裂允。(可獲取屬性的名稱等)
- value 屬性的新值
源碼閱讀
??????掌握了Kotllin的委托屬性如何使用后僻澎,還需要深入了解下委托屬性的源碼:
NotNullVar:Delegates.notNull()
延遲初始化的委托類
Delegates:Delegates
作為一個對象聲明存在祖乳,里面擁有3個非常熟悉的方法:notNull()
拳恋、observable
和vetoable
谬运。
ObservableProperty:ObservableProperty
系統(tǒng)定義的委托類梆暖,observable
和vetoable
返回該委托類的匿名對象轰驳。
Delegates.notNull()
??????Delegates.notNull()
直接返回NotNullVar
對象作為委托屬性的代理對象级解。
#Delegates.kt
public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
#Delegates.kt
private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
??????從源碼中可以看到其內(nèi)部實現(xiàn)與之前我們使用的支持屬性是一樣的原理勤哗,但他提供了getValue()
和setValue()
,使Delegates.notNull()
可以代理var
屬性芒划。
Delegates.observable()
??????Delegates.observable()
和Delegates.vetoable()
一樣,都是直接返回ObservableProperty的匿名對象拼苍。但Delegates.observable()
重載afterChange
函數(shù)调缨,并在afterChange
函數(shù)中執(zhí)行Delegates.observable()
接收的lambda棚点。ObservableProperty#setValue()
在對屬性賦新值后,將舊值和新值作為參數(shù)執(zhí)行afterChange
函數(shù)瘫析。
#Delegates.kt
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
#ObservableProperty.kt
protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
Delegates.vetoable()
??????Delegates.vetoable()
與Delegates.observable()
非常相似咸包,只是重載的函數(shù)不一致烂瘫,Delegates.vetoable()
重載beforeChange
函數(shù)。ObservableProperty
的getValue()
會先獲取beforeChange
函數(shù)的返回值(默認是true)葛账,判斷是否繼續(xù)執(zhí)行賦值操作籍琳。所以這就是Delegates.vetoable()
的不同的地方趋急。
#Delegates.kt
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}
#ObservableProperty.kt
protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val oldValue = this.value
//如果beforeChange返回false宣谈,則直接返回函數(shù),不賦值
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
委托屬性的原理
??????想要更深層次的了解Kotlin的委托嗦嗡,最好的辦法就是將其轉(zhuǎn)換成Java代碼進行研究侥祭。
#daqiKotlin.kt
class Person{
var name:String by Delegates.observable("daqi"){ property, oldValue, newValue ->
println("${property.name} oldValue = $oldValue newValue = $newValue")
}
}
反編譯后的Java代碼:
public final class Person$$special$$inlined$observable$1 extends ObservableProperty {
// $FF: synthetic field
final Object $initialValue;
public Person$$special$$inlined$observable$1(Object $captured_local_variable$1, Object $super_call_param$2) {
super($super_call_param$2);
this.$initialValue = $captured_local_variable$1;
}
protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
Intrinsics.checkParameterIsNotNull(property, "property");
String newValue = (String)newValue;
String oldValue = (String)oldValue;
int var7 = false;
String var8 = property.getName() + " oldValue = " + oldValue + " newValue = " + newValue;
System.out.println(var8);
}
}
public final class Person {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;"))};
@NotNull
private final ReadWriteProperty name$delegate;
@NotNull
public final String getName() {
return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
}
public Person() {
Delegates var1 = Delegates.INSTANCE;
Object initialValue$iv = "daqi";
ReadWriteProperty var5 = (ReadWriteProperty)(new Person$$special$$inlined$observable$1(initialValue$iv, initialValue$iv));
this.name$delegate = var5;
}
}
- 1次哈、創(chuàng)建一個繼承自
ObservableProperty
的Person$$special$$inlined$observable$1
類琼牧,因為Delegates.observable()
是返回一個匿名的ObservableProperty
對象。 - 2撬槽、
Person
類中定義了一個name$delegate
屬性,該屬性指向name
屬性的代理對象暂题,即Person$$special$$inlined$observable$1
類的對象敢靡。 - 3、
Person
類中name
屬性會轉(zhuǎn)換為getName()
和setName()
赶站。 - 4想括、
name
屬性的get
和set
方法的內(nèi)部調(diào)用name$delegate
相應的setValue()
和getValue()
瑟蜈。 - 5铺根、KProperty數(shù)組中會保存通過Kotlin反射得到的Personr類中的name屬性的信息。在調(diào)用
name$delegate
的setValue()
和getValue()
時掂林,將這些信息作為參數(shù)傳遞進去泻帮。
幕后字段與幕后屬性
??????你看完反編譯的Java源碼后刑顺,或許會發(fā)現(xiàn)一個問題:為什么Kotlin中Person
的name
屬性并沒有在Java的Person
中被定義狼讨,只實現(xiàn)了該屬性的get
和set
方法。
??????這其中涉及到Kotlin的幕后字段的問題布隔, Kotlin 什么是幕后字段衅檀? 中講得很清楚:
只有擁有幕后字段的屬性轉(zhuǎn)換成Java代碼時霎俩,才有對應的Java變量哀军。
Kotlin屬性擁有幕后字段需要滿足以下條件之一:
- 使用默認
getter
/setter
的屬性,一定有幕后字段打却。對于var
屬性來說杉适,只要getter
/setter
中有一個使用默認實現(xiàn),就會生成幕后字段; - 在自定義
getter
/setter
中使用了field
的屬性
??????所以也就能理解柳击,為什么擴展屬性不能使用 field
猿推,因為擴展屬性并不能真的在該類中添加新的屬性,不能具有幕后字段捌肴。而且委托屬性中蹬叭,該屬性的get
和set
方法內(nèi)部都是調(diào)用代理對象的getValue()
或setValue()
,并沒有使用 field
,且都不是使用默認的get
和set
方法。
總結(jié)
- 類委托可以很方便的實現(xiàn)裝飾設計模式铺坞,開發(fā)者只用關心需要擴展的方法。
- 委托屬性就是將該屬性的
set
和get
交由 代理對象 的setValue
和getValue
來處理叉弦。 - 委托屬性也是一種 約定 樱拴。
setValue
和getValue
都需帶有operator
關鍵字修飾正罢。 - Kotlin標準庫提供
ReadOnlyProperty
或ReadWriteProperty
接口呛占,方便開發(fā)者實現(xiàn)這些接口來提供正確的getValue()
方法 和setValue()
方法。 -
val
屬性可以借助 委托屬性 進行延遲初始化洪灯,使用lazy()
設置初始化流程铅檩,并自動返回代理對象。 -
Delegates.observable()
能在 被委托的屬性 改變時接收到通知,有點類似ACC的LiveData -
Delegates.vetoable()
能在 被委托的屬性 改變前接收通知,并能決定該屬性賦不賦予新值。 -
Delegates.notNull()
可以用作任何類型的var
變量進行 延遲初始化 - 只有擁有幕后字段的屬性轉(zhuǎn)換成Java代碼時丙曙,才有對應的Java變量索抓。
參考資料:
- 《Kotlin實戰(zhàn)》
- Kotlin官網(wǎng)
android Kotlin系列:
Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用