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

----《第一季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 实例的函数,返回的实例可以作为实现延迟属性的委托。第一次访问被委托变量(调用 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 = mapOf(
        "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) {
    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

class MapObject2(val map: MutableMap) {
    val key1: String by map
    val key2: String by map
    val key3: String by map
}

fun testMapObject2() {
    var map: MutableMap = mutableMapOf(
        "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()
}

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)