知 屬性,Kotlin 知識梳理(9) 委托屬性

 2023-10-08 阅读 26 评论 0

摘要:一、本文概要 本文是對<<Kotlin in Action>>的學習筆記,如果需要運行相應的代碼可以訪問在線環境 try.kotlinlang.org,這部分的思維導圖為: 二、委托屬性的基本操作 2.1 委托屬性的基本語法 class Foo {var p : Type by Delegate() } 復制代

一、本文概要

本文是對<<Kotlin in Action>>的學習筆記,如果需要運行相應的代碼可以訪問在線環境 try.kotlinlang.org,這部分的思維導圖為:

二、委托屬性的基本操作

2.1 委托屬性的基本語法

class Foo {var p : Type by Delegate()
}
復制代碼

類型為Type的屬性p將它的訪問器邏輯委托給了另一個Delegate實例,通過關鍵字by對其后的 表達式求值 來獲取這個對象,關鍵字by可以用于任何 符合屬性委托約定規則的對象

按照約定,Delegate類必須具有getValuesetValue方法,它們可以是成員函數,也可以是擴展函數,Delegate的簡單實現如下:

class Delegate {operator fun getValue(...) { ... }operator fun setValue(..., value : Type) { ... }
}
復制代碼

知 屬性。使用方法如下:

val foo = Foo()
val oldValue = foo.p
foo.p = newValue
復制代碼

當我們將foo.p作為普通屬性使用時,實際上將調用Delegate類型的輔助屬性的方法。為了研究這種機制如何在實踐中使用,我們首先看一個委托屬性展示威力的例子:庫對惰性初始化的支持

2.2 使用委托屬性:惰性初始化和 "by lazy()"

惰性初始化是一種常見的模式,直到 在第一次訪問該屬性 的時候,才根據需要創建對象的一部分。

2.2.1 使用支持屬性來實現惰性初始化

使用這種技術來實現惰性初始化時,需要兩個值,一個是對內部可見的可空_emails變量,另一個是提供對屬性的讀取訪問的email變量,它是非空的,在emailget()函數中首先判斷_emails變量是否為空,如果為空那么就先初始化它,否則直接返回。

2.2.2 使用委托屬性來實現惰性初始化

class Person(val name : String) {val emails by lazy { loadEmails(this) }
}
復制代碼

這里可以使用標準庫函數lazy返回的委托,lazy函數返回一個對象,該對象具有一個名為getValue且簽名正確的方法,因此可以把它與by關鍵字一起使用來創建一個委托屬性。lazy的參數是一個lambda,可以調用它來初始化這個值,默認情況下,lazy函數是線程安全的。

2.3 實現委托屬性

2.3.1 常規實現方式

五行屬性,要了解委托屬性的實現方式,讓我們來看另一個例子:當一個對象的屬性更改時通知監聽器。Java具有用于此類通知的標準機制:PropertyChangeSupportPropertyChangeEventPropertyChangeSupport類維護了一個監聽器列表,并向它們發送PropertyChangeEvent事件,要使用它,你通常需要把PropertyChangeSupport的一個實例存儲為bean類的一個字段,并將屬性更改的處理委托給它。

為了避免在每個類中去添加這個字段,你需要創建一個小的工具類,用來存儲PropertyChangeSupport的實例并監聽屬性更改,之后,你的類會繼承這個工具類,以訪問changeSupport

open class ValueChangeAware {   protected val changeSupport = PropertyChangeSupport(this)//添加監聽者。fun addListener(listener : PropertyChangeListener) {changeSupport.addPropertyChangeListener(listener)}//移除監聽者。fun removeListener(listener : PropertyChangeListener) {changeSupport.removePropertyChangeListener(listener)}
}//輔助類,如果通過該輔助類改變了屬性,那么將會通知監聽者。
class ObservableValue (val valueName : String, var valueValue : Int, val changeSupport : PropertyChangeSupport
) {fun getValue() : Int = valueValuefun setValue(newValue : Int) {val oldValue = valueValuevalueValue = newValue//通知監聽者。changeSupport.firePropertyChange(valueName, oldValue, newValue)}
}class Person(val name : String, age : Int) : ValueChangeAware() {// _age 為輔助類的一個實例。val _age = ObservableValue("age", age, changeSupport)//通過輔助類進行讀寫操作。var age : Intget() = _age.getValue()set(value) { _age.setValue(value) }
}
復制代碼

下面是實際應用的代碼:

fun main(args: Array<String>) {val person = Person("zemao", 20)person.addListener(//監聽者打印出改變的屬性名、原屬性值和新的屬性值。PropertyChangeListener { event ->                              println("${event.propertyName} " + "changed from ${event.oldValue} to ${event.newValue}" )})person.age = 18
}
復制代碼

運行結果為:

>> age changed from 20 to 18
復制代碼

2.3.2 使用 ObservableValue 作為屬性委托

在上面的代碼中,如果Person類中包含了多個與age類似的屬性,那么就需要創建多個_age的實例,并把gettersetter委托給它,Kotlin的委托屬性可以讓你擺脫這些樣板代碼,首先,我們需要重寫ObservableValue代碼,讓它符合屬性委托的約定。

class ObservableValue (var valueValue : Int, val changeSupport : PropertyChangeSupport
) {//按照約定的需要,用 operator 來標記,并添加了 KProperty。operator fun getValue(p : Person, prop : KProperty<*>) : Int = valueValueoperator fun setValue(p : Person, prop : KProperty<*>, newValue : Int) {val oldValue = valueValuevalueValue = newValue//通知監聽者。changeSupport.firePropertyChange(prop.name, oldValue, newValue)}
}
復制代碼

生肖屬性知識。和2.3.1相比,我們做了以下幾點修改:

  • 按照約定的需要,getValuesetValue函數被標記了operator
  • 這些函數加了兩個參數:一個用于接收屬性的實例,用來設置和讀取屬性,另一個用于表示屬性本身,這個屬性類型為KProperty,你可以使用KProperty.name的方式來訪問該屬性的名稱。

下面,我們再修改Person類,將age屬性委托給ObservableValue類:

class Person(val name : String, age : Int) : ValueChangeAware() {var age : Int by ObservableValue(age, changeSupport)
}
復制代碼

運行結果和2.3.1相同。

2.3.3 使用 Delegates.observable 來實現屬性修改的通知

Kotlin標準庫中,已經包含了類似于ObservableValue的類,因此我們不用手動去實現可觀察的屬性邏輯,下面我們重寫Person類:

class Person(val name: String, age: Int
) : ValueChangeAware() {private val observer = {prop: KProperty<*>, oldValue: Int, newValue: Int ->changeSupport.firePropertyChange(prop.name, oldValue, newValue)}var age: Int by Delegates.observable(age, observer)}
復制代碼

運行結果和以上兩小結相同。

2.4 委托屬性的變換規則

梳理。讓我們來總結一下委托屬性是怎么工作的,假設你已經有了一個具有委托屬性的類:

class Foo {var p : Type by Delegate()
}
復制代碼

Delegate實例將會被保存到一個隱藏的屬性中,它被稱為<delegate>,編譯器也將用一個KProperty類型的對象來表示這個屬性,它被稱為<property>,編譯器生成的的代碼如下:

class Foo {private val <delegate> = Delegate()var prop : Type {get() = <delegate>.getValue(this, <property>)set(value : Type) = <delegate>.setValue(this, <property>, value)}
}
復制代碼

因此,在每個屬性訪問器中,編譯器都會生成對應的getValuesetValue方法。

2.5 在 map 中保存屬性的值

委托屬性發揮作用的另一種常見用法是 用在動態定義的屬性集的對象中,這樣的對象有時被稱為 自訂對象。例如考慮一個聯系人管理系統,可以用來存儲有關聯系人的任意信息,系統中的每個人都有一些屬性需要特殊處理(例如名字),以及每個人特有的數量任意的額外屬性(例如,最小的孩子的生日)。

實現這種系統的一種方法是將人的所有屬性存儲在map中,不確定提供屬性,來訪問需要特殊處理的信息。

class Person {private val _attributes = hashMapOf<String, String>()fun setAttribute(attrName: String, value: String) {_attributes[attrName] = value}//把 map 作為委托屬性。val name: String by _attributes
}
復制代碼

kotlin函數,使用方式:

fun main(args: Array<String>) {val p = Person()val data = mapOf("name" to "Dmitry", "company" to "JetBrains")for ((attrName, value) in data)p.setAttribute(attrName, value)println(p.name)
}
復制代碼

因為標準庫已經在標準mapMutableMap接口上定義了getValuesetValue擴展函數,所以可以在這里直接調用。


更多文章,歡迎訪問我的 Android 知識梳理系列:

  • Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
  • 個人主頁:lizejun.cn
  • 個人知識總結目錄:lizejun.cn/categories/

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/130818.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息