每天学一点 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<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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容