委託,對於不少Java開發者來講都會一面矇蔽,我也不例外。委託,維基百科的解釋是:有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理。這好像有一點代理的味道(*゜ー゜*)。Kotlin中委託分爲類委託和委託屬性。java
在解釋類委託以前,須要先了解一波裝飾設計模式。裝飾設計模式的核心思想是:android
在不使用繼承的狀況下,擴展一個對象的功能,使該對象變得更增強大。設計模式
一般套路是:建立一個新類,新類實現與原始類同樣的接口,並將原來的類的實例做爲一個字段保存,與原始類擁有一樣的行爲(方法)。一部分行爲(方法)與原始類保持一致(即直接調用原始類的行爲(方法)),還有一部分行爲(方法)在原始類的行爲(方法)基礎上進行擴展。數組
裝飾設計模式的缺點是須要較多的樣板代碼,顯得比較囉嗦。例如:最原始的裝飾類須要實現接口的所有方法,並在這些方法中調用原始類對象對應的方法。安全
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
關鍵字,將新類的接口實現委託給原始類,編譯器會爲新類自動生成接口方法,並默認返回原始類對應的具體實現。而後咱們重載須要擴展的方法。bash
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
)委託給一個符合屬性委託約定規則的對象。異步
委託屬性和類委託不一樣,委託屬性更像是給屬性找代理。 委託屬性一樣是利用by
關鍵字,將屬性委託給代理對象。屬性的代理對象沒必要實現任何的接口,可是須要提供一個 getValue()
函數與 setValue()
函數(僅限 var
屬性)。例如:ide
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
修飾,意味着委託屬性也是依賴於約定的功能。像其餘約定的函數同樣,getValue()
和 setValue()
能夠是成員函數,也能夠是擴展函數。函數
Kotlin官方庫中提供 ReadOnlyProperty
或 ReadWriteProperty
接口,方便開發者實現這些接口來提供正確的 getValue()
方法 和 setValue()
方法。post
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
變量呢?可使用支持屬性來實現惰性初始化:
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提供了更加便捷的解決方案:委託屬性,並使用標準庫函數lazy
返回代理對象。
class Person{
val email:List<Email> by lazy {
ArrayList<Email>()
}
}
複製代碼
lazy
函數接收初始化該值操做的lambda,並返回一個具備getValue()
方法的代理對象,並配合by
關鍵字將屬性委託給lazy
函數返回的代理對象。lazy
函數是線程安全的,不用擔憂異步的問題。
當一個對象的屬性須要更改時獲得通知,最原始的辦法就是,重寫set方法,在set方法中設置處理屬性改變的邏輯。手工實現屬性修改的通知:
class Person(name:String,age:Int){
var age :Int = age
set(newValue) {
val oldValue = field
field = newValue
//監聽值改變(或使用Listener對象)
valueChangeListener("age",oldValue,newValue)
}
var name :String = name
set(newValue) {
val oldValue = field
field = newValue
//監聽值改變
valueChangeListener("name",oldValue,newValue)
}
fun <T> valueChangeListener(fieldName:String,oldValue:T,newValue:T){
println("$fieldName oldValue = $oldValue newValue = $newValue")
}
}
複製代碼
但這種方案在跟惰性初始化最開始的例子相似,當須要監聽多個屬性時,代碼冗長且囉嗦。咱們能夠像惰性初始化同樣,使用委託屬性實現:
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
//監聽值改變(或使用Listener對象)
observer(property,oldeValue,newValue)
}
}
複製代碼
定義委託類,經過委託屬性"接管"該屬性的get
/set
,並提供初始值,以及屬性被修改時的處理邏輯。大大簡化屬性設置監聽的代碼。
但Kotlin在標準庫中已經爲咱們提供了Delegates.observable()
方法,大大方便咱們使用委託屬性對屬性的修改進行監聽,像咱們自定義的委託類同樣,該方法接受屬性的初始化值,以及屬性變化時的處理邏輯:
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()
進行處理,依據Delegates.vetoable()
的返回的布爾值判斷要不要賦新值。
以前已經知道,var
屬性須要延遲初始化時,可使用lateinit
關鍵字,val
屬性須要延遲初始化時,可使用委託屬性 + lazy()
函數的方法。但lateinit
關鍵字的延遲處理僅對引用類型有用,對基本數據類型無效,當須要對基本數據類型進行延遲初始化怎麼辦呢?Kotlin經過委託屬性提供另外一種延遲初始化的方式:Delegates.notNull()
var num:Int by Delegates.notNull()
複製代碼
雖然Kotlin提供了延遲初始化的方式,使開發者不用強制在構造函數中初始化(例如Activity
中在onCreate
中初始化),但對於延遲初始化的值,必須確保其被初始化,不然將會像Java空指針同樣,拋出異常。
方式 | 適用類型 |
---|---|
lateinit | 引用類型 |
Delegates.notNull() | 基本數據類型、引用類型 |
每一個委託屬性的實現的背後,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)
}
複製代碼
掌握了Kotllin的委託屬性如何使用後,還須要深刻了解下委託屬性的源碼:
NotNullVar:Delegates.notNull()
延遲初始化的委託類
Delegates:Delegates
做爲一個對象聲明存在,裏面擁有3個很是熟悉的方法:notNull()
、observable
和vetoable
。
ObservableProperty:ObservableProperty
系統定義的委託類,observable
和vetoable
返回該委託類的匿名對象。
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
}
}
複製代碼
從源碼中能夠看到其內部實現與以前咱們使用的支持屬性是同樣的原理,但他提供了getValue()
和setValue()
,使Delegates.notNull()
能夠代理var
屬性。
Delegates.observable()
和Delegates.vetoable()
同樣,都是直接返回ObservableProperty的匿名對象。但Delegates.observable()
重載afterChange
函數,並在afterChange
函數中執行Delegates.observable()
接收的lambda。ObservableProperty#setValue()
在對屬性賦新值後,將舊值和新值做爲參數執行afterChange
函數。
#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.observable()
很是類似,只是重載的函數不一致,Delegates.vetoable()
重載beforeChange
函數。ObservableProperty
的getValue()
會先獲取beforeChange
函數的返回值(默認是true),判斷是否繼續執行賦值操做。因此這就是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,則直接返回函數,不賦值
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
複製代碼
想要更深層次的瞭解Kotlin的委託,最好的辦法就是將其轉換成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;
}
}
複製代碼
ObservableProperty
的Person$$special$$inlined$observable$1
類,由於Delegates.observable()
是返回一個匿名的ObservableProperty
對象。Person
類中定義了一個name$delegate
屬性,該屬性指向name
屬性的代理對象,即Person$$special$$inlined$observable$1
類的對象。Person
類中name
屬性會轉換爲getName()
和setName()
。name
屬性的get
和set
方法的內部調用name$delegate
相應的setValue()
和getValue()
。name$delegate
的setValue()
和getValue()
時,將這些信息做爲參數傳遞進去。 你看完反編譯的Java源碼後,或許會發現一個問題:爲何Kotlin中Person
的name
屬性並無在Java的Person
中被定義,只實現了該屬性的get
和set
方法。
這其中涉及到Kotlin的幕後字段的問題, Kotlin 什麼是幕後字段? 中講得很清楚:
只有擁有幕後字段的屬性轉換成Java代碼時,纔有對應的Java變量。
Kotlin屬性擁有幕後字段須要知足如下條件之一:
getter
/ setter
的屬性,必定有幕後字段。對於 var
屬性來講,只要 getter
/ setter
中有一個使用默認實現,就會生成幕後字段;getter
/ setter
中使用了 field
的屬性 因此也就能理解,爲何擴展屬性不能使用 field
,由於擴展屬性並不能真的在該類中添加新的屬性,不能具備幕後字段。並且委託屬性中,該屬性的get
和set
方法內部都是調用代理對象的getValue()
或setValue()
,並無使用 field
,且都不是使用默認的get
和set
方法。
set
和get
交由 代理對象 的setValue
和 getValue
來處理。setValue
和 getValue
都需帶有operator
關鍵字修飾。ReadOnlyProperty
或 ReadWriteProperty
接口,方便開發者實現這些接口來提供正確的 getValue()
方法 和 setValue()
方法。val
屬性能夠藉助 委託屬性 進行延遲初始化,使用lazy()
設置初始化流程,並自動返回代理對象。Delegates.observable()
能在 被委託的屬性 改變時接收到通知,有點相似ACC的LiveDataDelegates.vetoable()
能在 被委託的屬性 改變前接收通知,並能決定該屬性賦不賦予新值。Delegates.notNull()
能夠用做任何類型的var
變量進行 延遲初始化