ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
Kotlin标准库中,已经自定义了一系列标准委托,包括了大部分有用的委托。 可参考本文前面的系列文章——[委托属性](https://www.kancloud.cn/alex_wsc/android_kotlin/1318315) [TOC] ## 延迟加载(Lazy) 在Kotlin中,声明变量或者属性的同时要对其进行初始化,否则就会报异常,**尽管我们可以定义可空类型的变量,但有时却不想这样做,能不能在变量使用时再进行初始化呢**?为此,Kotlin中提供了延迟加载(又称懒加载)功能,当变量被访问时才会被初始化,这样不仅可以提高程序效率,还可以让程序启动更快。 延迟加载是通过“by lazy”关键字标识的,延迟加载的变量要求声明为val,即不可变变量,相当于Java中用final关键字修饰的变量。 **延迟加载也是委托的一种形式**,延迟加载的语法结构如下: ``` val/var变量:变量类型by lazy{ 变量初始化代码 } ``` >[info]需要注意的是,延迟加载的变量在第1次初始化时会输出代码块中的所有内容,之后在调用该变量时,都只会输出最后一行代码的内容。 ### 定义 lazy()是一个函数,接受一个Lambda表达式作为参数,返回一个Lazy类型的实例,这个实例可以作为一个委托,,实现延迟加载属性(lazy property):第一次调用 get() 时,将会执行 lazy() 函数受到的Lambda 表达式,然后会记住这次执行的结果,以后所有对 get() 的调用都只会简单地返回以前记住的结果。 ### 语法结构 ``` val/var 变量: 变量类型 by lazy{ 变量初始化代码 } ``` ### 案例分析 针对延迟加载lazy,我们需要知道两个结论。 1. 延迟属性只有在访问的时候才会被初始化。 2. 延迟属性只会初始化一次。 参考代码: ![](http://upload-images.jianshu.io/upload_images/7368752-8b9db857d0064590.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 针对以上代码,我们在7行打个断点,然后debug运行程序。可以看到【normalValue】已经赋值成功,而【lazyValue】则提示“Lazy value not initialized yet.”。 参考截图: ![](http://upload-images.jianshu.io/upload_images/7368752-d8a43e00da6d7733.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 之所以出现现在的情况,因为我们还没有访问到【lazyValue】,我们接着执行完第17行,将会看到lazyValue被初始化了。 参考截图: ![](http://upload-images.jianshu.io/upload_images/7368752-62eebf6720a9b6bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 我们接着执行完所有代码,看结果输出: ![](http://upload-images.jianshu.io/upload_images/7368752-e34a245acea82988.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 可以看到【println("lazyValue进行初始化")】只被输出了一次。所以,延迟属性lazy只初始化一次。 ## 可观察属性(Observable) ### 定义 Delegates.observable()函数接受两个参数: 第一个是初始化值,第二个是属性值变化事件的响应器(handler)。这种形式的委托,采用了观察者模式,其会检测可观察属性的变化,当被观察属性的setter()方法被调用的时候,响应器(handler)都会被调用(在属性赋值处理完成之后)并自动执行执行的lambda表达式,同时响应器会收到三个参数:被赋值的属性, 赋值前的旧属性值, 以及赋值后的新属性值。 ### 语法结构 ![](http://upload-images.jianshu.io/upload_images/7368752-d5c94e770f98a30c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 其中Lambda表达式(变化事件的响应器)会在变量的setter()被调用的时候触发。 Lambda里面可以获取三个参数: * prop-被赋值的属性; * old-赋值前的旧属性值; * new-赋值后的新属性值; ### 案例分析 参考代码: ![](http://upload-images.jianshu.io/upload_images/7368752-b1e2406f489547dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 针对以上代码,第13行和第16行代码对属性进行了修改,所以我们的属性变化事件响应器被触发了2次。 ## Vetoable ### 定义 Delegates.vetoable()函数接受两个参数:第一个是初始化值,第二个是属性值变化事件的响应器(handler),是可观察属性(Observable)的一个特例,不同的是在响应器指定的自动执行执行的lambda表达式中在保存新值之前做一些条件判断,来决定是否将新值保存。 ### 语法结构 ![](http://upload-images.jianshu.io/upload_images/7368752-f92ba244db06b95e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) * Lambda 返回true:变量会被修改 * Lambda返回false:变量不会被修改 ### 案例分析 参考代码: ![](http://upload-images.jianshu.io/upload_images/7368752-f51bbf5edb2beef5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 结果分析: 针对以上代码,代码在第15行和第19行,对userName进行了修改,每次修改都触发了Lambda表达式的执行。但是第15行新的属性值【"heima"】不满足【new.contains("黑马")】,所以没有赋值,所以16行输出结果是【userName当前属性值:黑马程序员】。 ## notNull ### 定义 **对于一个不可为“non-null”的变量,我们需要保证它被正确的赋值。赋值操作可以在变量定义的时候,也可以后续代码里面进行赋值。我们只需要在变量后面使用notNull属性委托,编译器就允许我们后续进行赋值**。 加了notNull的属性委托,编译器允许我们后续赋值,那我们也有可能忘记赋值,这样使用变量的时候就会报空指针。那有的同学可能会说,我已经养成了良好的习惯,在使用变量之前会进行非空判断,这样的做法会导致有大量的非空判断,这是不美观的。其实这个时候即使做非空判断也会抛出异常(UninitializedPropertyAccessException)。因为notNull的属性委托,要求变量被引用的时候被赋值。 ### 语法结构 ![](http://upload-images.jianshu.io/upload_images/7368752-939ee15010ba44c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 案例分析 参考代码: ![](http://upload-images.jianshu.io/upload_images/7368752-be4fe4483a2fa780.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 结果分析: 针对以上代码的第11行,因为我们使用了notNull的属性委托,所以编译器允许【userName】后续进行赋值。 针对以上代码的第16行对【user.userName】进行非空判断,然后在第17行进行使用,感觉上不会有什么问题,但是结果大家看到了,报了空指针异常。说明,**我们用了notNull属性委托之后,在第16行的时候用到了【user.userName】,这个时候如果我们没有赋值就会抛出异常,提示“lateinit propety userName has not been initialized”**。 ## 将多个属性保存在一个map内 ### 定义 Kotlin的map委托,提供了map和对象一一绑定关系,就是map的值可以决定对象的值,修改map的值也可以影响对象的值。但是这一切需要满足,map的key和属性的名称保证一致。 ### 语法结构 ``` val/var 变量: 变量类型by 实现Map接口的对象 ``` ### 案例分析 参考代码: ![](http://upload-images.jianshu.io/upload_images/7368752-248bd08d365e7d3a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 提示: 针对以上代码的第7行用到的【MutableMap】,也可以改为其他map,但是如果要想修改map集合里面的值,必须使用MutableMap接口对象。