kotlin延迟初始化和密封类详细讲解
作者:发飙的蜗牛YR 发布时间:2022-07-10 01:53:52
对变量延迟初始化
Kotlin语言有许多特性,包括变量不可变,变量不可为空,等等。这些特性都是为了尽可能地保证程序安全而设计的,但是有些时候这些特性也会在编码时给我们带来不少麻烦。
比如,你的类中存在许多全局变量实例,为了保证他们能够满足kotlin的空指针检查语法标准,你不得不做出许多的非空判断保护才行,即使你非常确定它们不会为空。
通过一个例子:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private var adapter:MsgAdapter?=null
override fun onCreate(savedInstanceState:Bundle?){
...
adapter=MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter?.notifyItemInserted(msgList.size-1)
...
}
}
这里我们将adapter设置了全局变量,但是它的初始化工作是在onCreate()方法中进行的,因此不得不先将adapter赋值为null,同时把它的类型声明成MsgAdapter?。
虽然我们会在onCreate()方法中对adapter进行初始化,同时能确保onClick()方法必然在onCreate()方法之后才会调用,但是我们在onClick()方法中调用adapter的任何方法时仍然要进行判空处理才行,否则编译肯定无法通过。
而当你的代码中有了越来越多的全局变量实例时,这个问题就会变得越来越明显,到时候你可能必须编写大量额外的判空处理代码,只是为了满足kotlin编译器的要求。
这个问题其实是可以解决的,而且非常简单,那就是对全局变量进行延迟初始化。
延迟初始化使用的是lateinit关键字,它可以告诉kotlin编译器,我会在晚些时候对这个变量进行初始化,这样我就不用在一开始的时候就将它赋值为null了。
接下来我就就使用延迟初始化的方式对上述代码进行优化,如下所示:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
adapter=MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter.notifyItemInserted(msgList.size-1)
...
}
}
可以看到,我们在adapter变量的前面加上lateinit关键字,这样就不用在一开始的时候将它赋值为null,同时类型声明也就可以改成MsgAdapter了,由于MsgAdapter是不可为空的类型,所以我们在onClick()方法中也就不再需要进行判空处理,直接调用adaper的任何方法就可以了。
当然,使用lateinit关键字也不是没有任何风险,如果在adapter变量还没有初始化的情况下就直接使用它,那么程序就一定会奔溃,并且抛出一个UninitializedPropertyAccessException异常。
所以,当你对一个全局变量使用了lateinit关键字时,请一定要确保它在被任何地方调用之前已经完成了初始化工作,否则Kotlin将无法保证程序的安全性。
另外,我们还可以通过代码来判断一个全局变量是否已经完成了初始化,这些某些时候能够有效地避免对某一个变量进行初始化操作,示例代码如下:
class MainActivity:AppCompatActivity(),View.onClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
if(!::adapter.isInitialized){
adapter=MsgAdapter(msgList)
}
...
}
}
::adapter.isInitialized可用于判断adapter变量是否已经初始化。然后我们再对结果进行取反,如果还没有初始化,那就立即对adapter变量进行初始化,否则什么都不做。
使用密封类优化代码
密封类通常可以结合RecyclerView适配器中的ViewHolder一起使用,它可以在很多时候帮助你写出更加规范和安全的代码。
通过一个例子:
新建一个Kotlin文件
interface Result{
}
class Success(val msg:String):Result
class Failure(val error:Exception):Result
这里定义一个Result接口,用于表示某个操作的执行结果,接口中不用编写任何内容,然后定义了两个类去实现Result接口:一个Success类用于表示成功时的结果,一个Failure类用于表示失败时的结果。
接下来定义一个getResultMsg()方法,用于获取最终执行结果的信息,代码如下:
fun getResultMsg(result: Result)=when(result){
is Success-> result.msg
is Failure-> result.error
else -> throw IllegalArgumentException()
}
getResultMsg()方法中接收一个Result参数,我们通过when语句来判断:如果Result属于Success,那么就返回成功的消息;如果Result属于Failure,那么就返回错误信息。接下来我们不得不再编写一个else条件,否则Kotlin编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上Result的执行结果只可能是Success或Failure,这个else条件永远走不到,所以我们在这里直接抛出异常,只是为了满足kotlin编译器的语法检查而已。
但是else还有一个潜在风险,如果我们现在新增一个Unkown类并实现Result接口,用于表示未知的执行结果,但是如果没有在getResultMsg()方法中添加相应的条件分支,编译器这种情况下不会提醒我们而是直接运行进入else条件里面。
这个时候密封类可以解决这个问题,密封类的关键字是sealed class,将Result接口改造成密封类的写法:
sealed class Result{
}
class Success(val msg:String): Result()
class Failure(val error:Exception):Result()
这个时候会发现getResultMsg()方法中的else条件已经不需要了,如下所示
fun getResultMsg(result: Result)=when(result){
is Success-> result.msg
is Failure-> result.error
}
这是因为当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你每一个子类所对应的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的情况。而如果我们新增一个Unknown类,并也让它继承自Result,此时getResultMsg()方法就一定会报错,必须新增一个Unknown的条件分支才能让代码编译通过。
密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的。
接下来看一下它是如何结合MsgAdapter中的ViewHolder一起使用,并优化一下MsgAdapter中的代码。
比如在MsgAdapter中的onBindViewHolder()方法中存在一个没有实际作用的else条件,只是抛出一个异常而已。对于这部分的代码,我们就可以借助密封类的特性来进行优化。新建一个MsgViewHolder.kt文件,其中加入如下代码:
sealed class MsgViewHolder(view:View):RecyclerView.ViewHolder(view){
}
class LeftViewHolder(view: View):MsgViewHolder(view){
val leftMsg:TextView=view.findViewById(R.id.leftMsg)
}
class RightViewHolder(view: View):MsgViewHolder(view){
val rightMsg:TextView=view.findViewById(R.id.rightMsg)
}
这里我们定义了一个密封类MsgViewHolder,并让他继承自RecyclerView.ViewHolder,然后让leftViewHolder和RightViewHolder继承自MsgViewHolder。这样就相当于密封类MsgViewHolder只有两个已知子类,因此在when语句中只要处理这两种情况的条件分支即可。
修改MsgAdapter代码,如下所示:
class MsgAdapter(val msgList:List<msg>):RecyclerView.Adapter<MsgViewHolder>(){
...
override fun onBindViewHolder(holder:MsgViewHolder,position:Int){
val msg=msgList[position]
when(holder){
is LeftViewHolder -> holder.leftMsg.text=msg.content
is RightViewHolder -> holder.rightMsg.text=msg.content
}
}
...
}
这里我们将RecycleView.Adapter的泛型指定成刚刚定义的密封类MsgViewHolder,这样onBindViewHolder()方法传入的参数就变成了MsgViewHolder。然后我们只要在when语句当中处理LeftViewHolder和RightViewHolder这两种情况就可以了,else也不需要了。这种RecyclerView适配器的写法更加规范也更加推荐。
来源:https://blog.csdn.net/ChenYiRan123456/article/details/127797126


猜你喜欢
- 本文实例讲述了C#导出数据到Excel文件的方法。分享给大家供大家参考。具体实现方法如下:/// <summary>/// 导出
- 本文实例为大家分享了在Android中如何实现下拉导航选择菜单效果的全过程,供大家参考,具体内容如下关于下拉导航选择菜单效果在新闻客户端中用
- 前言:由于最近有解析协议的一些业务场景,需要用到一些字节操作工具,这里封装了一些比较常用的转换方法,测试后基本没有问题,可能一些比较偏门的数
- Synchronized的用法在多线程并发问题中,常用Synchronized锁解决问题。Synchronized锁通常用于同步示例方法,同
- 本文实例为大家分享了Android实现层叠卡片式banner的具体代码,供大家参考,具体内容如下效果图如下:背景由于公司VIP模块项目需要,
- 效果图:A.绘制圆环,圆弧,文本//1.画圆环//原点坐标float circleX = width / 2;float circleY =
- 1、有状态的bean与无状态的bean有状态bean:每个用户有自己特有的一个实例,在用户的生存期内,bean保存了用户的信息,即有状态;一
- 我们初学java的第一个程序是"hello world" public class HelloWorld {
- 资源下载:点此下载一、语言和环境1.实现语言: JAVA语言。2.环境要求: MyEclipse/Eclipse + Tomcat + My
- 有一位程序员去相亲的时候,非常礼貌得说自己是一名程序员,并解释自己是做底层架构的,于是女方听到"底层"两个字,就一脸嫌弃
- 消息的保存路径消息发送端发送消息到 broker 上以后,消息是如何持久化的?数据分片kafka 使用日志文件的方式来保存生产者和发送者的消
- @ModelAttribute在父类、子类的执行顺序被 @ModelAttribute 注解的方法会在Controller每个方法执行之前都
- 1.问题项目中有自己企业的通讯录,但是在应用中拨打公司通讯录的联系人,由于手机通讯录中没有相应的信息,只显示一串电话号2 .目的监听系统来电
- java编程中字节流转换成字符流的实现方法import java.io.*;/*readLine方法是字符流BufferReader类中的方
- 可以理解当我们要调用一个方法时,我们会把指定的数值,传递给方法中的参数,这样方法中的参数就拥有了这个指定的值,可以使用该值,在方法中运算了。
- 前言我们了解数组这个概念之前,我们先思考下面几个问题。如果我们需要两个数据,那么直接创建两个变量即可int a;int b;如果需要五个数据
- 经常坐地铁,却不知道地铁多少条线路?哪个站下车?今天就带领大家熟悉并绘制深圳地铁路线图。WPF在绘制矢量图方面有非常强大的优势,利用WPF可
- 本文实例讲述了Android编程实现canvas绘制饼状统计图功能。分享给大家供大家参考,具体如下:本例的目的是实现一个简单的饼状统计图,效
- 这篇文章主要介绍了配置springboot项目使用外部tomcat过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作
- Annotation(注解)是JDK1.5及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以&