美文网首页
20.Kotlin属性委托

20.Kotlin属性委托

作者: leofight | 来源:发表于2018-05-01 20:46 被阅读0次

    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<String>) {
        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 <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 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<String>) {
        println(myLazyValue)
        println(myLazyValue)
    }
    
    

    输出

    hello world
    30
    30
    

    非空属性

    示例代码

    class MyPerson {
        var address: String by Delegates.notNull<String>()
    }
    
    fun main(args: Array<String>) {
        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<String>) {
    
        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<String, Any?>) {
    
        val name: String by map
    
        val address: String by map
    
        val age: Int by map
    
        val birthday: Date by map
    }
    
    
    class Student2(map: MutableMap<String, Any?>) {
    
        var address: String by map
    }
    
    fun main(args: Array<String>) {
        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<String, Any?> = 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<People, String> {
        override fun getValue(thisRef: People, property: KProperty<*>): String {
            return "hello world"
        }
    }
    
    class PeopleLauncher {
        operator fun provideDelegate(thisRef: People, property: KProperty<*>): ReadOnlyProperty<People, String> {
            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<String>) {
        val people = People();
    
        println(people.name)
        println(people.address)
    }
    

    输出

    welcome
    welcome
    hello world
    hello world
    

    相关文章

      网友评论

          本文标题:20.Kotlin属性委托

          本文链接:https://www.haomeiwen.com/subject/xogxrftx.html