委托属性的语法
val/var <属性名>:<类型> by <表达式>
在by后面的表达式是改委托,因为属性对应的get()(与set())会被委托给它的getValue()与setValue()方法。属性的委托不必实现任何接口,但需要提供一个getValue()函数(var属性还需要提供setValue()函数)
例如:
fun main(){
val e=Example()
println(e.d)
e.d=6
}
class Example{
var d:Int by DelegateTest()
}
class DelegateTest{
operator fun getValue(thisRef:Any?,property:KProperty<*>):Int{
return 1
}
operator fun setValue(thisRef: Any?,property: KProperty<*>,value:Int){
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
输出的结果:
1
6 has been assigned to 'd' in com.hx.learn.Example@433c675d.
从输出的结果可以知道,当我们从读取实例的d的值的时候,会调用委托类的getValue()函数,当我们给实例的d赋值的时候,会调用setValue()函数。
从上面委托类的两个函数中看到都包含了thisRef
和property
两个参数的,第一个参数是读出d的对象,第二个参数保存了对d自身的描述。
属性委托的要求
- 对于一个只读属性(即val声明的),委托必须提供一个操作函数getValue(),getValue()的函数的返回值必须与属性相同类型(或者是其值类型),并且函数具有以下参数
thisRef
必须与属性所有者类型相同或者是其超类。property
必须是类型KProperty<*>
或者是其超类。
- 对于一个可变属性(即var声明的),委托必须额外提供一个操作函数setValue(),该函数具有以下参数:
thisRef
必须与属性所有者类型相同或者是其超类。property
必须是类型KProperty<*>
或者是其超类。value
必须与属性类型相同(或者是其超类)
标准委托
Kotlin标准库为几种有用的委托提供了工厂方法:
延迟属性Lazy
lazy()
是接受一个lambda并返回一个Lazy
fun main(){
val lazyValue:String by lazy{
println("你好")
"lazy"
}
println(lazyValue)
println("-----------")
println(lazyValue)
}
在默认的情况下,对于lazy属性的求值是同步锁的:该值只在前一个线程中计算,并且所有线程看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将
LazyThreadSafetyMode.PUBLICATION
作为参数传递给lazy()函数。而如果你确定初始化将总是发生在与属性使用位于相同的线程,那么可以使用LazyThreadSafetyMode.NONE
作为Lazy()函数的参数,它不会有任何线程安全的保证以及 相关的开销。
可观察属性Observable
Delegates.observable()
接受两个参数:初始化与修改时处理程序。每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有是三个参数:被赋值的属性,旧值与新值。
例如:
fun main(){
var person=Person()
person.age=15
person.age=16
}
class Person{
var age:Int by Delegates.observable(10){
property, oldValue, newValue ->
println("${property.name}:{$oldValue}->{$newValue}")
}
}
结果:
age:{10}->{15}
age:{15}->{16}
observable()是在赋值之后调用处理程序的,如果想在属性被赋新值生效前会调用处理程序可以使用vetoable()。
voteable的处理程序中返回的是布尔值,如果返回为ture则让赋值生效,否则赋值不生效。
委托给另一个属性
从kotlin 1.4开始,一个属性可以帮它的getter与setter委托给另一个属性。该委托属性可以是:
- 顶层属性
- 同一个类的成员或扩展属性
- 另一个类的成员或扩展属性
将一个属性委托给另一个属性,应在委托名称中使用::
限定符。当想要以一种向后兼容的方式命名一个属性性时,使用@Deprecated注解来注解旧的属性,并委托其实现。
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
myClass.oldName = 42
println(myClass.newName) // 输出42
}
将属性储存在映射中
常见的用例就是在一个映射(map)里面存储的值,可以使用映射实例自身作为委托来实现委托属性。
例如:
fun main(){
var person=Person(
mapOf("name" to "Kotlin"
,"age" to 10)
)
}
class Person(map:Map){
val age:Int by map
val name:String by map
}
委托属性会从这个映射中取值(通过字符串键——属性的名称)
println(person.age)
println(person.name)
输出:
10
Kotlin
如果是var属性的,需要将只读的Map换成MutableMap。
局部委托属性
fun main(){
example(2)
}
fun example(value:Int){
val tempInt by lazy {
println("第一次被调用")
value
}
println(tempInt)
println(tempInt)
}
结果:
第一次被调用
2
2
tempInt变量只有在第一才访问时,才会执行处理程序,之后访问就不再执行。