委托属性:依赖于约定的功能,是 Kotlin 中最独特和最强大的功能之一。
一、基本知识
语法:
val/var <属性名>: <类型> by <表达式>
其中,在 by 后面的表达式是委托,属性对应的 get() 与 set() 会被委托给它的 getValue() 与 setValue() 方法。按照约定,表达式类必须具有 getValue 和 setValue 方法(setValue 方法仅适用于可变属性)
import kotlin.reflect.KProperty
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun setValue(example: Example, property: KProperty<*>, s: String) {
println("$s has been assigned to '${}' in $example.")
}
operator fun getValue(example: Example, property: KProperty<*>): String {
return "$example, thank you for delegating '${}' to me!"
}
}
fun main(args: Array<String>){
val example = Example()
example.p = "hello world!!!"
println(example.p)
}
结果:
hello world!!! has been assigned to ‘p’ in Example@1f17ae12.
Example@1f17ae12, thank you for delegating ‘p’ to me!
二、使用委托属性
1、惰性初始化 和 “by lazy()” 【延迟属性】
惰性初始化,类似于懒人模式,直到在第一次访问该属性的时候,才根据需要创建对象的一部分。
class Person(val name: String){
private var _emails: List<Email>? = null
val emails: List<Email>
get(){
if(_emails == null){
_emails = loadEmails(this)
}
return _emails!!
}
}
fun main(args: Array<String>){
val p = Person("Alice")
p.emails
}
支持属性:类中有一个属性,_emails,而另一个属性 emails,用来提供对属性的读取访问。_emails 可以为空,而 emails 为非空。
上述代码并不是线程安全的,在 Kotlin 中提供的解决方案:使用委托属性来实现惰性初始化,默认情况下,对于 lazy 属性的求值是同步锁的
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
//val lazyValue: String by lazy ({
// println("computed!")
// "Hello"
//})
fun main() {
println(lazyValue)
println(lazyValue)
}
结果:
computed!
Hello
Hello
lazy() 是接收一个 lambda 并返回一个 Lazy< T > 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式 并 记录结果,后续调用 get() 就只是返回记录的结果。
2、可观察属性 Observable
() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
结果:
<no name> -> first
first -> second
三、实现委托属性
从 Kotlin 1.4 开始,一个属性可以把它的 getter 与 setter 委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。该委托属性可以为:1)顶层属性;2)同一个类的成员或扩展属性; 3)另一个类的成员或扩展属性。
实例:
写法1:当一个人的年龄或工资发生变化时,进行通知
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
//Person类,当一个人的年龄或工资发生变化时,这个类将通知它的监听器
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
var age : Int = age
set(newValue) {
val oldVaule = field
field = newValue
changeSupport.firePropertyChange("age", oldVaule, newValue)
}
var salary : Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange("salary", oldValue, newValue)
}
}
fun main(args:Array<String>){
val p = Person("Zxy", 25, 10000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 29
p.salary = 30000
}
可以看出,setter 中有很多重复的代码,提取一个类来存储,代码如下:
写法2:当一个人的年龄或工资发生变化时,进行通知。简化写法1
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
class ObservableProperty(val propName:String, var propValue:Int, val changeSupport: PropertyChangeSupport){
fun getValue():Int = propValue
fun setValue(newValue: Int){
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
//Person类,当一个人的年龄或工资发生变化时,这个类将通知它的监听器
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
val _age = ObservableProperty("age", age, changeSupport)
var age: Int
get() = _age.getValue()
set(value) { _age.setValue(value) }
val _salary = ObservableProperty("salary", salary, changeSupport)
var salary: Int
get() = _salary.getValue()
set(value) { _salary.setValue(value) }
}
fun main(args:Array<String>){
val p = Person("Zxy", 25, 10000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 29
p.salary = 30000
}
上述代码将属性的 getter 和 setter 委托给 ObservableProperty 类,在 Kotlin 中属性委托功能可以让 程序员 摆脱这些样板代码。
使用 ObservableProperty 类作为属性委托
写法3:当一个人的年龄或工资发生变化时,进行通知。使用属性委托
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.reflect.KProperty
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
class ObservableProperty(var propValue:Int, val changeSupport: PropertyChangeSupport){
// p,用来接收属性的实例;prop,用于表示属性本身
operator fun getValue(p: Person, prop: KProperty<*>):Int = propValue
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int){
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
//Person类,当一个人的年龄或工资发生变化时,这个类将通知它的监听器
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
var age: Int by ObservableProperty(age, changeSupport)
var salary: Int by ObservableProperty(salary, changeSupport)
}
fun main(args:Array<String>){
val p = Person("Zxy", 25, 10000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 29
p.salary = 30000
}
写法4:不用手动实现 ObservableProperty 类,而是使用 Kotlin 标准库,它已经包含了类似于 ObservableProperty 的类。
标准库和 PropertyChangeSupport 类没有耦合,因此需要传递一个 lambda,来告诉它如何通知属性值的更改
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
//Person类,当一个人的年龄或工资发生变化时,这个类将通知它的监听器
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int -> changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(age, observer)
var salary: Int by Delegates.observable(salary, observer)
}
fun main(args:Array<String>){
val p = Person("Zxy", 25, 10000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed " +
"from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 29
p.salary = 50000
}
by 右边的表达式,其值,只要是能够被编译器用正确的参数类型来调用 getValue 和 setValue 的对象即可。
四、在 map 中保存属性值
写法1:定义属性,将值存入 map
class Person{
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName:String, value:String){
_attributes[attrName] = value
}
val name:String
get() = _attributes["name"]!!
}
fun main(args:Array<String>){
val p = Person()
val data = mapOf("name" to "Zxy", "company" to "psbc")
for((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name)
}
写法2:使用委托属性把值存到 map 中
class Person{
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName:String, value:String){
_attributes[attrName] = value
}
val name:String by _attributes //由于标准库已在 Map 和 MutableMap 接口上定义了 getValue 和 setValue 扩展函数,所以可以把 map 作为委托属性
}
fun main(args:Array<String>){
val p = Person()
val data = mapOf("name" to "Zxy", "company" to "psbc")
for((attrName, value) in data)
p.setAttribute(attrName, value)
println(p.name)
}