美文网首页
Kotlin 学习笔记 (七)属性代理 及 委托

Kotlin 学习笔记 (七)属性代理 及 委托

作者: _明川 | 来源:发表于2019-05-23 17:05 被阅读0次
    CuracaoTurtle_ZH-CN5468901173_1920x1080.jpg

    前言
    本文章只是用于记录学习,所以部分地方如果有错误或者理解不对的地方,麻烦请指正。本篇为 csdn 原文章 转移修改版 原文章

    Kotlin 学习笔记(六)

    简述:

    1. lateinit 的使用
    2. 委托 和 委托属性
    3. 标准委托 和延迟委托

    1. lateinit

      在我们java 中我们在定义一个对象的时候通常可以不进行赋值

     private int index;
    

    但是在 kotlin 中如果我们同样这样写 就会提示我们应该进行一个初始化,当然我们也可以通过 latelnit 来设置为 该对象是 稍后初始化

    var index: int   // 报错
    var index :int = 1   // 直接初始化完成
    var latelnit index :int   // 稍后初始化
    

    2. Kotlin 委托

      委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

      Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

      委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base

    interface Base {
        fun print()
    }
    
    class BaseImpl(val x: Int) : Base {
        override fun print() { print(x) }
    }
    
    class Derived(b: Base) : Base by b
    
    // 如果没有委托我们就需要重写该接口
    
    class Derived(b: Base) : Base {
        override fun print() {
            TODO("not implemented")
        }
    }
    
    fun main(args: Array<String>) {
        val b = BaseImpl(10)
        Derived(b).print()
    }
    

    2.1 覆盖由委托实现的接口成员

      覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。如果将 override fun print() { print("abc") } 添加到 Derived,那么当调用 print 时程序会输出“abc”而不是“10”:

    interface Base {
        fun printMessage()
        fun printMessageLine()
    }
    
    class BaseImpl(val x: Int) : Base {
        override fun printMessage() { print(x) }
        override fun printMessageLine() { println(x) }
    }
    
    class Derived(b: Base) : Base by b {
        override fun printMessage() { print("abc") }
    }
    
    fun main(args: Array<String>) {
        val b = BaseImpl(10)
        Derived(b).printMessage()     // 打印出 abc
        Derived(b).printMessageLine()   // 没有被覆盖 会打印 10
    }
    

    3. 委托属性

    有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:

    1. 延迟属性(lazy properties): 其值只在首次访问时计算;
    2. 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
    3. 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。

    为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性:

    class Example {
        var p: String by Delegate()
    }
    
    class Delegate {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return "$thisRef, thank you for delegating '${property.name}' to me!"
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
            println("$value has been assigned to '${property.name}' in $thisRef.")
        }
    }
    
    fun main(args: Array<String>) {
        val e = Example()
        println(e.p)
    
        e.p = "ymc"
    }
    

    输出结果

    Example@300ffa5d, thank you for delegating 'p' to me!
    ymc has been assigned to 'p' in Example@300ffa5d.
    

    3.1 标准委托

    延迟委托

    语法:val/var <属性名>: <类型> by <表达式>

      属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性),因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。也可以直接继承 ReadWriteProperty ,实现其中的方法,这样就避免了自己手写可能出现的错误,例如 Kotlin 源码中这样实现判空的委托属性:

    public object Delegates {
      public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
    }
    
    private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
        private var value: T? = null
    
        public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
        }
    
        public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            this.value = value
        }
    }
    

      其中 NotNullVar 继承了 ReadWriteProperty,并实现了他的两个方法,而Delegates.notNull() 属于委托属性。

    3.2 委托要求

    (一脸懵逼的地方)

      对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数(可以继承 ReadOnlyProperty 实现该方法):

    1. thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型,

    2. property —— 必须是类型 KProperty<*> 或其超类型,

    3. 对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数(可以继承 ReadWriteProperty 实现该方法):

      1. thisRef —— 同 getValue(),

      2. property —— 同 getValue(),

      3. new value —— 必须和属性同类型或者是它的超类型。

      getValue() 或/和 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。

    3.3 翻译规则

      在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

    class C {
        var prop: Type by MyDelegate()
    }
    
    // 这段是由编译器生成的相应代码:
    class C {
        private val prop$delegate = MyDelegate()
        var prop: Type
            get() = prop$delegate.getValue(this, this::prop)
            set(value: Type) = prop$delegate.setValue(this, this::prop, value)
    }
    
    

      Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

    4. 标准委托

    4.1延迟委托

      lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

    val lazyValue: String by lazy {
        println("computed!")
        "Hello"
    }
    
    fun main(args: Array<String>) {
        println(lazyValue)
        println(lazyValue)
    }
    // 输出: 
    computed!  // 只有第一次 初始化
    Hello
    Hello
    

      默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证以及相关的开销。

    4.2 可观察属性 Observable

      Delegates.observable() 接受两个参数:初始值 与 修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:

    import kotlin.properties.Delegates
    
    class User {
        var name: String by Delegates.observable("<no name>") {
            prop, old, new ->
            println("$old -> $new")
        }
    }
    
    fun main(args: Array<String>) {
        val user = User()
        user.name = "first"
        user.name = "second"
    }
    

    运行结果

    <no name> -> first
    first -> second
    

    4.3 属性保存在映射中

      一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

    // 由于 Map只读,我们如果需要 var 可以使用 MutableMap 替换
    
    class User(val map: Map<String, Any?>) {
        val name: String by map
        val age: Int     by map
    }
    
    fun main(args: Array<String>) {
        val user = User(mapOf(
            "name" to "John Doe",
            "age"  to 25
        ))
        println(user.name) // Prints "John Doe"
        println(user.age)  // Prints 25
    }
    

    Kotlin 学习笔记(八)

    相关文章

      网友评论

          本文标题:Kotlin 学习笔记 (七)属性代理 及 委托

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