----《第一季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.png2.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
网友评论