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

标签:kotlin,延迟初始化,密封类
0
投稿

猜你喜欢

  • C#实现XML序列化与反序列化

    2022-10-06 19:50:21
  • SpringCloud Gateway HttpWebHandlerAdapter链路调用请求流程介绍

    2023-04-29 00:12:18
  • java如何获得redis所有的key-value

    2022-03-13 12:22:14
  • java增强for循环的实现方法

    2023-12-07 16:42:53
  • Spring中的后置处理器BeanPostProcessor详解

    2023-02-24 00:50:03
  • Java日常练习题,每天进步一点点(10)

    2022-08-11 21:55:53
  • JSON 与对象、集合之间的转换的示例

    2021-12-04 20:08:58
  • Android UI系列-----ScrollView和HorizontalScrollView的详解

    2022-04-06 14:14:08
  • 线程池ThreadPoolExecutor并行处理实现代码

    2022-10-13 23:44:01
  • Spring boot @RequestBody数据传递过程详解

    2022-09-14 12:23:37
  • Android EditText自定义样式的方法

    2021-10-06 22:37:37
  • C#算法之各位相加

    2021-09-03 17:32:42
  • java swing 创建一个简单的QQ界面教程

    2022-09-08 06:51:39
  • Android简易音乐播放器实现代码

    2021-12-22 23:26:53
  • Android开发5:应用程序窗口小部件App Widgets的实现(附demo)

    2023-04-22 20:15:36
  • java读取文件内容,解析Json格式数据方式

    2021-10-07 13:56:23
  • Springcloud seata分布式事务实现代码解析

    2022-12-27 20:14:01
  • DevExpress TreeList 常见问题解决方法

    2022-07-15 18:16:54
  • Java swing五子棋的实现方法

    2021-06-01 15:25:20
  • C#使用Aspose.Cells创建和读取Excel文件

    2022-11-24 17:47:23
  • asp之家 软件编程 m.aspxhome.com