Kotlin知識概括(十) —— 委託

前序

      委託,對於不少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)
    }

}
複製代碼

委託屬性

委託屬性就是將屬性的訪問器(getset)委託給一個符合屬性委託約定規則的對象。異步

      委託屬性和類委託不一樣,委託屬性更像是給屬性找代理。 委託屬性一樣是利用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官方庫中提供 ReadOnlyPropertyReadWriteProperty 接口,方便開發者實現這些接口來提供正確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)
}
複製代碼
  • thisRef表示持有該委託屬性的對象
  • property KProperty<*> 類型或是它的父類,屬性的描述。(可獲取屬性的名稱等)
  • value 屬性的新值

源碼閱讀

      掌握了Kotllin的委託屬性如何使用後,還須要深刻了解下委託屬性的源碼:

NotNullVar:Delegates.notNull()延遲初始化的委託類

Delegates:Delegates做爲一個對象聲明存在,裏面擁有3個很是熟悉的方法:notNull()observablevetoable

ObservablePropertyObservableProperty系統定義的委託類,observablevetoable返回該委託類的匿名對象。

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
    }
}
複製代碼

      從源碼中能夠看到其內部實現與以前咱們使用的支持屬性是同樣的原理,但他提供了getValue()setValue(),使Delegates.notNull()能夠代理var屬性。

Delegates.observable()

      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.vetoable()Delegates.observable()很是類似,只是重載的函數不一致,Delegates.vetoable()重載beforeChange函數。ObservablePropertygetValue()會先獲取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;
   }
}
複製代碼
  • 一、建立一個繼承自ObservablePropertyPerson$$special$$inlined$observable$1類,由於Delegates.observable()是返回一個匿名的ObservableProperty對象。
  • 二、Person類中定義了一個name$delegate屬性,該屬性指向name屬性的代理對象,即Person$$special$$inlined$observable$1類的對象。
  • 三、Person類中name屬性會轉換爲getName()setName()
  • 四、name屬性的getset方法的內部調用name$delegate相應的setValue()getValue()
  • 五、KProperty數組中會保存經過Kotlin反射獲得的Personr類中的name屬性的信息。在調用name$delegatesetValue()getValue()時,將這些信息做爲參數傳遞進去。

幕後字段與幕後屬性

      你看完反編譯的Java源碼後,或許會發現一個問題:爲何Kotlin中Personname屬性並無在Java的Person中被定義,只實現了該屬性的getset方法。

      這其中涉及到Kotlin的幕後字段的問題, Kotlin 什麼是幕後字段? 中講得很清楚:

只有擁有幕後字段的屬性轉換成Java代碼時,纔有對應的Java變量。

Kotlin屬性擁有幕後字段須要知足如下條件之一:

  • 使用默認 getter / setter 的屬性,必定有幕後字段。對於 var 屬性來講,只要 getter / setter 中有一個使用默認實現,就會生成幕後字段;
  • 在自定義 getter / setter 中使用了 field 的屬性

      因此也就能理解,爲何擴展屬性不能使用 field,由於擴展屬性並不能真的在該類中添加新的屬性,不能具備幕後字段。並且委託屬性中,該屬性的getset方法內部都是調用代理對象的getValue()setValue(),並無使用 field ,且都不是使用默認的getset方法。

總結

  • 類委託能夠很方便的實現裝飾設計模式,開發者只用關心須要擴展的方法。
  • 委託屬性就是將該屬性的setget交由 代理對象 的setValuegetValue來處理。
  • 委託屬性也是一種 約定 。setValuegetValue都需帶有operator關鍵字修飾。
  • Kotlin標準庫提供 ReadOnlyPropertyReadWriteProperty 接口,方便開發者實現這些接口來提供正確getValue()方法 和 setValue()方法。
  • val屬性能夠藉助 委託屬性 進行延遲初始化,使用lazy()設置初始化流程,並自動返回代理對象。
  • Delegates.observable()能在 被委託的屬性 改變時接收到通知,有點相似ACC的LiveData
  • Delegates.vetoable()能在 被委託的屬性 改變前接收通知,並能決定該屬性賦不賦予新值。
  • Delegates.notNull()能夠用做任何類型的var變量進行 延遲初始化
  • 只有擁有幕後字段的屬性轉換成Java代碼時,纔有對應的Java變量。

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索