kotlin延迟初始化和密封类详细讲解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

kotlin延迟初始化和密封类详细讲解

发飙的蜗牛YR   2022-11-25 我要评论

对变量延迟初始化

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适配器的写法更加规范也更加推荐。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们