20.Kotlin属性委托

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

你可能感兴趣的:(20.Kotlin属性委托)