本文是对<<Kotlin in Action>>
的学习笔记,若是须要运行相应的代码能够访问在线环境 try.kotlinlang.org,这部分的思惟导图为: java
class Foo {
var p : Type by Delegate() } 复制代码
类型为Type
的属性p
将它的访问器逻辑委托给了另外一个Delegate
实例,经过关键字by
对其后的 表达式求值 来获取这个对象,关键字by
能够用于任何 符合属性委托约定规则的对象。安全
按照约定,Delegate
类必须具备getValue
和setValue
方法,它们能够是成员函数,也能够是扩展函数,Delegate
的简单实现以下:函数
class Delegate {
operator fun getValue(...) { ... }
operator fun setValue(..., value : Type) { ... }
}
复制代码
使用方法以下:工具
val foo = Foo()
val oldValue = foo.p
foo.p = newValue
复制代码
当咱们将foo.p
做为普通属性使用时,实际上将调用Delegate
类型的辅助属性的方法。为了研究这种机制如何在实践中使用,咱们首先看一个委托属性展现威力的例子:库对惰性初始化的支持。学习
惰性初始化是一种常见的模式,直到 在第一次访问该属性 的时候,才根据须要建立对象的一部分。this
使用这种技术来实现惰性初始化时,须要两个值,一个是对内部可见的可空_emails
变量,另外一个是提供对属性的读取访问的email
变量,它是非空的,在email
的get()
函数中首先判断_emails
变量是否为空,若是为空那么就先初始化它,不然直接返回。spa
class Person(val name : String) {
val emails by lazy { loadEmails(this) }
}
复制代码
这里可使用标准库函数lazy
返回的委托,lazy
函数返回一个对象,该对象具备一个名为getValue
且签名正确的方法,所以能够把它与by
关键字一块儿使用来建立一个委托属性。lazy
的参数是一个lambda
,能够调用它来初始化这个值,默认状况下,lazy
函数是线程安全的。线程
要了解委托属性的实现方式,让咱们来看另外一个例子:当一个对象的属性更改时通知监听器。Java
具备用于此类通知的标准机制:PropertyChangeSupport
和PropertyChangeEvent
。PropertyChangeSupport
类维护了一个监听器列表,并向它们发送PropertyChangeEvent
事件,要使用它,你一般须要把PropertyChangeSupport
的一个实例存储为bean
类的一个字段,并将属性更改的处理委托给它。code
为了不在每一个类中去添加这个字段,你须要建立一个小的工具类,用来存储PropertyChangeSupport
的实例并监听属性更改,以后,你的类会继承这个工具类,以访问changeSupport
。cdn
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 = valueValue
fun setValue(newValue : Int) {
val oldValue = valueValue
valueValue = newValue
//通知监听者。
changeSupport.firePropertyChange(valueName, oldValue, newValue)
}
}
class Person(val name : String, age : Int) : ValueChangeAware() {
// _age 为辅助类的一个实例。
val _age = ObservableValue("age", age, changeSupport)
//经过辅助类进行读写操做。
var age : Int
get() = _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
复制代码
在上面的代码中,若是Person
类中包含了多个与age
相似的属性,那么就须要建立多个_age
的实例,并把getter
和setter
委托给它,Kotlin
的委托属性可让你摆脱这些样板代码,首先,咱们须要重写ObservableValue
代码,让它符合属性委托的约定。
class ObservableValue (
var valueValue : Int,
val changeSupport : PropertyChangeSupport
) {
//按照约定的须要,用 operator 来标记,并添加了 KProperty。
operator fun getValue(p : Person, prop : KProperty<*>) : Int = valueValue
operator fun setValue(p : Person, prop : KProperty<*>, newValue : Int) {
val oldValue = valueValue
valueValue = newValue
//通知监听者。
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
复制代码
和2.3.1
相比,咱们作了如下几点修改:
getValue
和setValue
函数被标记了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
相同。
在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)
}
复制代码
运行结果和以上两小结相同。
让咱们来总结一下委托属性是怎么工做的,假设你已经有了一个具备委托属性的类:
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)
}
}
复制代码
所以,在每一个属性访问器中,编译器都会生成对应的getValue
和setValue
方法。
委托属性发挥做用的另外一种常见用法是 用在动态定义的属性集的对象中,这样的对象有时被称为 自订对象。例如考虑一个联系人管理系统,能够用来存储有关联系人的任意信息,系统中的每一个人都有一些属性须要特殊处理(例如名字),以及每一个人特有的数量任意的额外属性(例如,最小的孩子的生日)。
实现这种系统的一种方法是将人的全部属性存储在map
中,不肯定提供属性,来访问须要特殊处理的信息。
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
//把 map 做为委托属性。
val name: String by _attributes
}
复制代码
使用方式:
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)
}
复制代码
由于标准库已经在标准map
和MutableMap
接口上定义了getValue
和setValue
扩展函数,因此能够在这里直接调用。