Kotlin属性委托(delegated property)
示例代码
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "$thisRef,you delegated property name is ${property.name}"
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) = println("${thisRef},new value is $value")
}
class MyPropertyClass {
var str: String by MyDelegate()
}
fun main(args: Array) {
val myPropertyClass = MyPropertyClass();
myPropertyClass.str = "hello world"
println(myPropertyClass.str)
}
输出
com.leofight.kotlin4.MyPropertyClass@439f5b3d,new value is hello world
com.leofight.kotlin4.MyPropertyClass@439f5b3d,you delegated property name is str
语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。
有4种情况在实际开发中比较有用:
1.延迟属性。
2.可观测属性
3.非空属性
4.map属性
延迟属性
延迟属性: 指的是属性只在第一次访问的时候才会计算,之后则会将之前的计算结果缓存起来供后续调用
lazy()
是接受一个 lambda 并返回一个 Lazy
实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get()
会执行已传递给 lazy()
的 lambda 表达式并记录结果, 后续调用 get()
只是返回记录的结果。
默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证和相关的开销。
LazyThreadSafetyMode
1.SYNCHRONIZED:默认情况下,延迟属性的计算是同步的:值只会在一个线程中得到计算,所有线程都会使用相同的一个结果。
2.PUBLICATION:如果不需要初始化委托的同步,这样多个线程可以同时执行。
3.NONE:如果确定初始化操作只会在一个线程中执行,这样会减少线程安全方面的开销。
示例代码:
val myLazyValue: Int by lazy {
println("hello world")
30
}
fun main(args: Array) {
println(myLazyValue)
println(myLazyValue)
}
输出
hello world
30
30
非空属性
示例代码
class MyPerson {
var address: String by Delegates.notNull()
}
fun main(args: Array) {
val myPerson = MyPerson();
myPerson.address = "suzhou"
println(myPerson.address)
}
输出
suzhou
如果注释掉myPerson.address = "suzhou"
,则输出
Exception in thread "main" java.lang.IllegalStateException: Property address should be initialized before get.
at kotlin.properties.NotNullVar.getValue(Delegates.kt:55)
at com.leofight.kotlin4.MyPerson.getAddress(HelloKotlin4.kt)
at com.leofight.kotlin4.HelloKotlin4Kt.main(HelloKotlin4.kt:15)
notNull适用于那些无法在初始化阶段就确定属性值的场合。
可观察属性(Observable)
示例代码
class Person{
var age: Int by Delegates.observable(20){
prop,oldValue,newValue -> println("${prop.name},oldValue: $oldValue,newValue: $newValue")
}
}
class Person2{
var age: Int by Delegates.vetoable(20){
prop,oldValue,newValue -> when{
oldValue <= newValue -> true
else -> false
}
}
}
fun main(args: Array) {
val person = Person();
person.age = 30
person.age = 40
println("-------")
val person2 = Person2();
println(person2.age)
person2.age = 40
println(person2.age)
println("=========")
person2.age = 30
println(person2.age)
输出
age,oldValue: 20,newValue: 30
age,oldValue: 30,newValue: 40
-------
20
40
=========
40
Delegates.observable接收两个参数:初始值与修改处理器。
处理器会在我们每次对属性赋值时得到调用(在赋值完成之后被调用)
处理器本身接收3个参数:被赋值的属性本身,旧的属性与新的属性
Delegates.vetoable的调用时机与Delegates.observable相反,它是在对属性赋值之前被调用,根据Delegates.vetoable的返回结果是true还是false,来决定是否真正对属性进行赋值。
map委托
将属性存储到map中
一种常见的应用场景是将属性值存储到map当中。
这通常出现在JSON解析或是一些动态行为。
在这种情况中,你可以将map实例作为委托,作为类中属性的委托。
map中的key的名字要与类中属性的名字保持一致
示例代码
class Student(map: Map) {
val name: String by map
val address: String by map
val age: Int by map
val birthday: Date by map
}
class Student2(map: MutableMap) {
var address: String by map
}
fun main(args: Array) {
val student = Student(mapOf(
"name" to "zhangsan",
"address" to "beijing",
"age" to 20,
"birthday" to Date()
))
println(student.name)
println(student.address)
println(student.age)
println(student.birthday)
println("--------")
val map: MutableMap = mutableMapOf(
"address" to "bejing"
)
val student2 = Student2(map)
println(map["address"])
println(student2.address)
println("---------")
student2.address = "shanghai"
println(map["address"])
println(student2.address)
}
输出
zhangsan
beijing
20
Tue May 01 16:55:19 CST 2018
--------
bejing
bejing
---------
shanghai
shanghai
关于属性委托的要求
对于只读属性来说(val修饰的属性),委托需要提供一个名为getValue的方法,该方法接收如下参数:
- thisRef,需要是属性拥有者相同的类型或者其父类型(对于扩展属性来说,这个类型指的被扩展的那个类型)
- property,需要是KProperty<*>类型或是其父类型
getValue方法需要返回与属性相同的类型或是其子类型
对于可变属性来说(var修饰的属性),委托需要再提供一个名为serValue的方法,该方法需要接收如下参数:
- thisRef,与getValue的thisRef要求一致
- property,与getValue方法的property要求一致。
- new value,需要与属性的类型相同或其父类型
getValue与setValue方法既可以作为委托类的成员方法实现,也可以作为其扩展方法来实现。
这两个方法都必须要标记为operator关键字。对于委托类来说,它可以实现ReadOnlyProperty或是ReadWriterProperty接口,这些接口包含了相应的getValue与setValue方法。同时,对于委托类来说,也可以不去实现这两个接口,而是自己单独实现相应的getValue与setValue方法。
委托转换规则
对于每个委托属性来说,Kotlin编译器在底层会生成一个辅助的属性,然后将原有属性的访问委托给这个辅助属性。
比如说,对于属性prop来说,Kotlin编译器所生成的隐含的属性名为prop$delegate属性,然后对原有的prop属性的访问器的访问都只是委托给这个额外的,Kotlin编译器所生成的辅助属性。
提供委托(providing a delegate)
通过定义provideDelegate operator,我们可以扩展委托的创建逻辑过程。如果对象定义了provideDelegate方法,那么该方法就会被调用来创建属性委托实例。
示例代码
class PropertyDelgate : ReadOnlyProperty {
override fun getValue(thisRef: People, property: KProperty<*>): String {
return "hello world"
}
}
class PeopleLauncher {
operator fun provideDelegate(thisRef: People, property: KProperty<*>): ReadOnlyProperty {
println("welcome")
when (property.name) {
"name", "address" -> return PropertyDelgate()
else -> throw Exception("not valid name")
}
}
}
class People {
val name: String by PeopleLauncher()
val address: String by PeopleLauncher()
}
fun main(args: Array) {
val people = People();
println(people.name)
println(people.address)
}
输出
welcome
welcome
hello world
hello world