Kotlin學習之類與對象篇—委託屬性

定義委託屬性

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

委託屬性的定義語法是:val/var <屬性名>: <類型> by <表達式>。by 後面的表達式就是委託, 屬性的get()/set()將被委託給它的getValue()setValue()方法。屬性委託不必實現任何接口,但是它們必須提供getValue()方法(如果是var屬性還必須提供setValue()方法)。

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.")
    }
}

當我們從委託給Delegate實例的屬性p讀取值時,Delegate的getValue()方法就會被調用,它的第一個參數是我們讀取p的對象,第二個參數是p自身的描述。

val e = Example()
println(e.p)

//打印出來的內容爲:
Example@33a17727, thank you for delegating ‘p’ to me!

同樣,當我們賦值給p,setValue()方法就會被調用,其前兩個參數和getValue()一樣,第三個參數爲即將被賦予的值:

e.p = "NEW"

上述代碼給p賦值NEW,將打印出:

NEW has been assigned to ‘p’ in Example@33a17727.


標準委託

Kotlin標準庫提供了幾個有用的委託方法

Lazy

lazy()是一個接收一個lambda並返回一個lazy<T>實例的方法,返回的實例能作爲實現了lazy屬性的委託:初次調用get()時執行lambda並記住其結果,當再次調用get()時就只返回結果。

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

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

上述代碼將打印出:

computed!
Hello
Hello

第一次調用lazyValue時,會執行lambda中的語句,打印出computed!,並返回結果hello,再次調用時不會執行lambda,只返回結果hello

默認情況下,對lazy屬性的求值是同步鎖(synchronized)的:值只在一個線程中計算,所有的線程都會看到同一個值。如果不要求初始化委託的同步鎖,以便多個線程可以併發執行它,那麼可以給lazy()方法傳一個參數LazyThreadSafetyMode.PUBLICATION。如果可以確定初始化總是發生在單一線程中,那麼可以使用LazyThreadSafetyMode.NONE模式,它不會有任何線程安全保障及相關的開銷。

Observable

Delegates.observable()接受兩個參數:一個初始化值 和 一個修改處理器。每當我們給屬性賦值時(在賦值執行之後),處理器就會被調用。處理器有三個參數,被賦值的屬性、原有值以及新值。

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

如果想在賦值執行之前攔截它並決定是否執行,可以使用vetoable()方法來替代observable()。vetoable()的處理器會在賦值執行之前被調用。

var max: Int by Delegates.vetoable(0) { 
    property, oldValue, newValue ->
        newValue > oldValue
}

println(max) // 0
max = 10
println(max) // 10
max = 5
println(max) // 10

在處理器中返回一個Boolean值,如果返回true,表示賦值將會被執行;如果返回false,則不執行。

在Map中保存屬性

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

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

代理屬性從map中取值(通過字符串key–屬性的名字)

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

這種方法對於var屬性也起作用,如果用MutableMap替代只讀的Map的話:

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int by map
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章