Kotlin 學習筆記 (七)屬性代理 及 委託

前言
本文章只是用於記錄學習,所以部分地方如果有錯誤或者理解不對的地方,麻煩請指正。

Kotlin 學習筆記(六)

簡述:

  1. lateinit 的使用
  2. 委託 和 委託屬性
  3. 標準委託 和延遲委託

1. lateinit

  在我們java 中我們在定義一個對象的時候通常可以不進行賦值

 private int index;

但是在 kotlin 中如果我們同樣這樣寫 就會提示我們應該進行一個初始化,當然我們也可以通過 latelnit 來設置爲 該對象是 稍後初始化

var index: int   // 報錯
var index :int = 1   // 直接初始化完成
var latelnit index :int   // 稍後初始化

2. Kotlin 委託

  委託模式是軟件設計模式中的一項基本技巧。在委託模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另一個對象來處理。

  Kotlin 直接支持委託模式,更加優雅,簡潔。Kotlin 通過關鍵字 by 實現委託。

  委託模式已經證明是實現繼承的一個很好的替代方式, 而 Kotlin 可以零樣板代碼地原生支持它。 Derived 類可以通過將其所有公有成員都委託給指定對象來實現一個接口 Base

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

// 如果沒有委託我們就需要重寫該接口

class Derived(b: Base) : Base {
    override fun print() {
        TODO("not implemented")
    }
}

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print()
}

2.1 覆蓋由委託實現的接口成員

  覆蓋符合預期:編譯器會使用 override 覆蓋的實現而不是委託對象中的。如果將 override fun print() { print(“abc”) } 添加到 Derived,那麼當調用 print 時程序會輸出“abc”而不是“10”:

interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).printMessage()     // 打印出 abc
    Derived(b).printMessageLine()   // 沒有被覆蓋 會打印 10
}

3. 委託屬性

有一些常見的屬性類型,雖然我們可以在每次需要的時候手動實現它們, 但是如果能夠爲大家把他們只實現一次並放入一個庫會更好。例如包括:

  1. 延遲屬性(lazy properties): 其值只在首次訪問時計算;
  2. 可觀察屬性(observable properties): 監聽器會收到有關此屬性變更的通知;
  3. 把多個屬性儲存在一個映射(map)中,而不是每個存在單獨的字段中。

爲了涵蓋這些(以及其他)情況,Kotlin 支持 委託屬性:

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

fun main(args: Array<String>) {
    val e = Example()
    println(e.p)

    e.p = "ymc"
}

輸出結果

Example@300ffa5d, thank you for delegating 'p' to me!
ymc has been assigned to 'p' in Example@300ffa5d.

3.1 標準委託

延遲委託

語法:val/var <屬性名>: <類型> by <表達式>

  屬性的委託不必實現任何的接口,但是需要提供一個 getValue() 函數(和 setValue()——對於 var 屬性),因爲屬性對應的 get()(和 set())會被委託給它的 getValue() 和 setValue() 方法。也可以直接繼承 ReadWriteProperty ,實現其中的方法,這樣就避免了自己手寫可能出現的錯誤,例如 Kotlin 源碼中這樣實現判空的委託屬性:

public object Delegates {
  public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
}

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
    }
}

  其中 NotNullVar 繼承了 ReadWriteProperty,並實現了他的兩個方法,而Delegates.notNull() 屬於委託屬性。

3.2 委託要求

(一臉懵逼的地方)

  對於一個只讀屬性(即 val 聲明的),委託必須提供一個名爲 getValue 的函數,該函數接受以下參數(可以繼承 ReadOnlyProperty 實現該方法):

  1. thisRef —— 必須與 屬性所有者 類型(對於擴展屬性——指被擴展的類型)相同或者是它的超類型,

  2. property —— 必須是類型 KProperty<*> 或其超類型,

  3. 對於一個可變屬性(即 var 聲明的),委託必須額外提供一個名爲 setValue 的函數,該函數接受以下參數(可以繼承 ReadWriteProperty 實現該方法):

    1. thisRef —— 同 getValue(),

    2. property —— 同 getValue(),

    3. new value —— 必須和屬性同類型或者是它的超類型。

  getValue() 或/和 setValue() 函數可以通過委託類的成員函數提供或者由擴展函數提供。 當你需要委託屬性到原本未提供的這些函數的對象時後者會更便利。 兩函數都需要用 operator 關鍵字來進行標記。

3.3 翻譯規則

  在每個委託屬性的實現的背後,Kotlin 編譯器都會生成輔助屬性並委託給它。 例如,對於屬性 prop,生成隱藏屬性 prop$delegate,而訪問器的代碼只是簡單地委託給這個附加屬性:

class C {
    var prop: Type by MyDelegate()
}

// 這段是由編譯器生成的相應代碼:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

  Kotlin 編譯器在參數中提供了關於 prop 的所有必要信息:第一個參數 this 引用到外部類 C 的實例而 this::prop 是 KProperty 類型的反射對象,該對象描述 prop 自身。

4. 標準委託

4.1延遲委託

  lazy() 是接受一個 lambda 並返回一個 Lazy 實例的函數,返回的實例可以作爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}
// 輸出: 
computed!  // 只有第一次 初始化
Hello
Hello

  默認情況下,對於 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個線程中計算,並且所有線程會看到相同的值。如果初始化委託的同步鎖不是必需的,這樣多個線程可以同時執行,那麼將 LazyThreadSafetyMode.PUBLICATION 作爲參數傳遞給 lazy() 函數。 而如果你確定初始化將總是發生在單個線程,那麼你可以使用 LazyThreadSafetyMode.NONE 模式, 它不會有任何線程安全的保證以及相關的開銷。

4.2 可觀察屬性 Observable

  Delegates.observable() 接受兩個參數:初始值 與 修改時處理程序(handler)。 每當我們給屬性賦值時會調用該處理程序(在賦值後執行)。它有三個參數:被賦值的屬性、舊值與新值:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

運行結果

<no name> -> first
first -> second

4.3 屬性保存在映射中

  一個常見的用例是在一個映射(map)裏存儲屬性的值。 這經常出現在像解析 JSON 或者做其他“動態”事情的應用中。 在這種情況下,你可以使用映射實例自身作爲委託來實現委託屬性。

// 由於 Map只讀,我們如果需要 var 可以使用 MutableMap 替換

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

fun main(args: Array<String>) {
    val user = User(mapOf(
        "name" to "John Doe",
        "age"  to 25
    ))
    println(user.name) // Prints "John Doe"
    println(user.age)  // Prints 25
}

Kotlin 學習筆記(八)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章