美文网首页
每天学一点 Kotlin -- 多彩的类:委托2

每天学一点 Kotlin -- 多彩的类:委托2

作者: 冯可乐同学 | 来源:发表于2021-11-26 16:50 被阅读0次

    ----《第一季Kotlin崛起:次世代Android开发 》学习笔记

    总目录:每天学一点 Kotlin ---- 目录
    上一篇:每天学一点 Kotlin -- 多彩的类:委托1
    下一篇:每天学一点 Kotlin -- 对象进阶:对象类型

    1. 委托方法

    可以使用 Kotlin 标准库中内置的工厂方法来实现委托,标准委托的方法有很多,最常用的几种有:延迟属性 Lazy,应用可观察属性 Observable,应用映射 map,Delegates.notNull<类型>。

    2. 延迟属性:Lazy

    2.1 通过 lazy,可以定义一个懒加载的属性,该属性的初始化不会在类创建的触发,而是在第一次用到的时候赋值。并且第一次调用 get 会执行已传递给 lazy 的 Lambda 表达式并记录结果,后续调用 get 只是返回记录的结果。

    2.2 举个栗子:

    val LazyShuXing:String by lazy {
        println("表达式")
        "结果1"
    }
    
    fun testLazy01(){
        var i = 1
        while ( i < 5){
            i ++
            print("$LazyShuXing \n")
        }
    }
    

    打印结果:

    表达式
    结果1 
    结果1 
    结果1 
    结果1
    

    而且写代码时,编译器会会提示 lazy 标识,如图所示:

    Snipaste_2021-11-26_14-24-16.png

    2.3 上面的代码中:定义了一个 String 类型的变量 LazyShuxing,并将它通过延迟属性 lazy 来实现委托。在调用函数中,使用 while 循环输出4次 LazyShuxing 的值。在打印结果中,只有第一次结果是 Lambda 表达式和结果1,其他都是结果1。

    2.4 在上面代码的基础上,增加几个结果,代码和打印如下:

    val LazyShuXing:String by lazy {
        println("表达式")
        "结果1"
        "结果2"
        "结果3"
        "结果4"
    }
    
    fun testLazy01(){
        var i = 1
        while ( i < 5){
            i ++
            println("call shuXing -- start")
            print("$LazyShuXing \n")
            println("call shuXing -- end")
        }
    }
    
    fun main() {
        testLazy01()
    }
    

    在写代码时,可以看到编译器自动的 lazy 标识指向了最后一个结果。打印结果:

    call shuXing -- start
    表达式
    结果4 
    call shuXing -- end
    call shuXing -- start
    结果4 
    call shuXing -- end
    call shuXing -- start
    结果4 
    call shuXing -- end
    call shuXing -- start
    结果4 
    call shuXing -- end
    

    从结果可知:当存在多个结果时,记录的仅是最后一个结果

    2.5 结论
    lazy 是接受一个 Lambda 并返回一个 Lazy<T> 实例的函数,返回的实例可以作为实现延迟属性的委托。第一次访问被委托变量(调用 get())会执行已经传递给 lazy 的 Lambda 表达式并记录结果。之后无论访问多少次,被委托的变量都只是返回记录的结果。

    2.6 注意:如果属性被设置为 var 类型(变量,而不是常量),那么它就不能被设置为延迟属性。这很好理解:因为 lazy 没有 setValue() 方法。

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

    3. 可观察属性:Observable

    3.1 顾名思义,可观察就是当属性发生变化时,可以观察到它的变化并将该变化输出。

    3.2 表达式是通过 Delegates.observable() 函数实现的,Delegates.observable() 函数接受两个参数:
    (1) 第一个是初始化值,
    (2) 第二个是属性值变化事件的响应器(handler) ,在属性赋值后会执行事件的响应器(handler)。 handler 它有三个参数,即被赋值的属性、旧值和新值。

    3.3 简单来说,每当更新可观察属性的值时,它都能保存之前的旧值和更新的值。也可以访问这些值。

    3.4 举个栗子:
    打印结果:

    class Watchable {
        var value: String by Delegates.observable("初始值") { prop, old, new ->
            println("old:${old}, new: ${new}")
        }
    }
    
    fun testObservable1() {
        val watchable = Watchable()
        watchable.value = "新的赋值"
        watchable.value = "再次新的赋值"
    }
    
    fun main() {
        testObservable1()
    }
    
    old:初始值, new: 新的赋值
    old:新的赋值, new: 再次新的赋值
    

    从上述结果可知:new 变量的值是第 n 次的赋值,old 变量的值是第 n-1 次的赋值。

    3.5 从上面的代码和结果分析:理解并使用可观察属性并不难。而且观察属性 和 观察者模式非常相似,可以说在某些场景下,可观察属性 比 观察属者模式更加方便。应该要在项目多加灵活运用。

    4. map:Kotlin 标准库中属性委托的应用映射

    4.1 map 想必都不会很陌生,它是一种存取数据的格式,通过键值对的方式对数据进行操作。那么在委托属性中的映射 map 又是怎样的呢?在某些特殊的情况(如动态
    事件)下,可以使用映射实例自身作为委托实现委托属性。

    4.2 分别看一下普通映射 和 在声明类的构造函数中接受一个映射,然后通过定义类的对象对映射进行操作。

    4.2.1 普通映射:

    fun testPuTongMap1() {
        val oneMap: Map<String, String> = mapOf<String, String>(
            "key1" to "value1",
            "key2" to "value2",
            "key3" to "value3"
        )
    
        println("start print oneMap")
        println("key1 = ${oneMap.get("key1")}")
        println("key2 = ${oneMap["key2"]}")
        println("key3 = ${oneMap["key3"]}")
    }
    
    fun main() {
        testPuTongMap1()
    }
    

    打印结果:

    start print oneMap
    key1 = value1
    key2 = value2
    key3 = value3
    

    4.2.2 通过定义类的对象对映射进行操作

    class MapObject(val map: Map<String, String>) {
        val key1: String by map
        val key2: String by map
        val key3: String by map
    }
    
    fun testMapObject1() {
        val oneMap = MapObject(
            mapOf(
                "key1" to "value1",
                "key2" to "value2",
                "key3" to "value3"
            )
        )
    
        println("start print oneMap")
        println("testMapObject1() -- key1 = ${oneMap.key1}")
        println("testMapObject1() -- key2 = ${oneMap.key2}")
        println("testMapObject1() -- key3 = ${oneMap.key3}")
    }
    
    fun main() {
        testMapObject1()
    }
    

    打印结果:

    start print oneMap
    testMapObject1() -- key1 = value1
    testMapObject1() -- key2 = value2
    testMapObject1() -- key3 = value3
    

    直观地从代码中比较发现,使用委托的映射中,是把 map 的 key 变成了对象的属性,可以通过 “对象.属性”的方式获取 map 中 key 对应的值。

    4.3 对于普通的映射,将映射的变量设置为 var 型可对映射中的值进行读取和更新。
    但是对于委托属性中的映射,将 val 改成 va 时, 编译器会提示错误“ rror Kotlin:
    Missing ’setValue(Mymap, KPrope y<*>, String)' method on delegate of type ’Map<String, String ’”, 意思是 map 中不存在 setValue() 方法,因此不能给对象的属性赋新值,所以只能使用另外一种方法更新映射中的值,那就是使用 MutableMap Map 。举个栗子:

    class MapObject2(val map: MutableMap<String, String>) {
        val key1: String by map
        val key2: String by map
        val key3: String by map
    }
    
    fun testMapObject2() {
        var map: MutableMap<String, String> = mutableMapOf<String, String>(
            "key1" to "value1",
            "key2" to "value2",
            "key3" to "value3"
        )
        val oneMap = MapObject2(map)
    
        println("start print oneMap")
        println("testMapObject2() -- key1 = ${oneMap.key1}")
        println("testMapObject2() -- key2 = ${oneMap.key2}")
        println("testMapObject2() -- key3 = ${oneMap.key3}")
    
        println("修改 oneMap 之后:")
        map.put("key1", "v1")
        map.put("key2", "v2")
        map.put("key3", "v3")
        println("testMapObject2() -- key1 = ${oneMap.key1}")
        println("testMapObject2() -- key2 = ${oneMap.key2}")
        println("testMapObject2() -- key3 = ${oneMap.key3}")
    }
    
    fun main() {
        testMapObject2()
    }
    

    打印结果:

    start print oneMap
    testMapObject2() -- key1 = value1
    testMapObject2() -- key2 = value2
    testMapObject2() -- key3 = value3
    修改 oneMap 之后:
    testMapObject2() -- key1 = v1
    testMapObject2() -- key2 = v2
    testMapObject2() -- key3 = v3
    

    4.4 总结:把 map 的 key 变成了类中的属性,并没发现有啥很好的优点...

    5. Delegates.notNull<类型>

    5.1 这个方法是 Kotlin 中已经实现好的方法,可以在需要的时候直接拿来用。通过它实现委托属性能判断访问的属性是否初始化。

    5.2 查看 notNull() 源码,当访问属性时会调用 getValue() 方法,它会自动判断属性的值是否为 Null。如果是,则抛出 IllegalStateException 错误并提示 property should be initialized before get ,意思属性在获取前应该被初始化(property.name 为属性 名称);如果不为 null,则返回属性的值。当给属性赋值时则调用 setValue() 方法。 notNull() 中的 setValue() 方法与普通代理类中的 setValue() 方法完全一样的。

    5.3 举个栗子:

    import kotlin.properties.Delegates
    
    class JudgeClass {
        var myName: String by Delegates.notNull<String>()
    }
    
    fun testNotNull1() {
        var judge = JudgeClass()
        // println("赋值前:")
        // println("judge.myName = ${judge.myName}") // 运行报错: java.lang.IllegalStateException: Property myName should be initialized before get
    
        judge.myName = ""
        println("赋值之后,但是赋值为空字符串:")
        println("judge.myName = ${judge.myName}")
    
        judge.myName = "Kotlin"
        println("赋值之后,赋值不是空字符串:")
        println("judge.myName = ${judge.myName}")
    }
    
    fun main() {
        testNotNull1()
    
    }
    

    打印结果:

    赋值之后,但是赋值为空字符串:
    judge.myName = 
    赋值之后,赋值不是空字符串:
    judge.myName = Kotlin
    
    相关代码:https://gitee.com/fzq.com/test-demo

    相关文章

      网友评论

          本文标题:每天学一点 Kotlin -- 多彩的类:委托2

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