开发者

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

开发者 https://www.devze.com 2022-11-28 12:49 出处:网络 作者: 发飙的蜗牛YR
目录对变量延迟初始化使用密封类优化代码对变量延迟初始化Kotlin语言有许多特性,包括变量不可变,变量不可为空,等等。这些特性都是为了尽可能地保证程序安全而设计的,但是有些时候这些特性也会在编码时给我...
目录
  • 对变量延迟初始化
  • 使用密封类优化代码

对变量延迟初始化

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-python1)
  ...
}
}

这里我们将adapter设置了全局变量,但是它的初始化工作是在onCreate()方法中进行的,因此不得不先将adapter赋值为null,同时把它的类型声明成MsgAdapter?。

虽然我们会在onCreate()方法中对adapter进行初始化,同时能确保onClick()方法必然在onCreate()方法之后才会调用,但是我们在onClick()方法中调用adapter的任何方法时仍然要进行判空处理才行,否则编译肯定无法通过。

而当你的代码中有了越来越多的全局变量实例时,这个问题就会变得越来越明显,到时候你可能必须编写大量额外的判空处理代码,只是为了满足kotlin编译器的要求。

这个问题其实是可以解决的,而且非常简单,那就是对全局变量进行延迟初始化。

延迟初始化使用的是lateinit关键字,它可以告诉kotlin编译器,我会在晚些时候对这个变量进行初始化,这样我就不用在一开始的时候就将它赋值为null了。

接下来我就就使用延迟初始化的方式对上述代码进行优化,如下所示:

class MainAc编程客栈tivity: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,将Rjsesult接口改造成密封类的写法:

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 MsgViewH开发者_JS开发older(view:View):RecyclerView.ViewHolder(view){
}
class  LeftViewHolder(view: View):MsgViewHolder(view){
    val leftMsg:TextView=view.findViewById(R.id.leftMsg)
}
class RightViewHolder(view: View):MsphpgViewHolder(view){
    val rightMsg:TextView=view.findViewById(R.id.rightMsg)
}

这里我们定义了一个密封类MsgViewHolder,并让他继承自RecyclerView.ViewHolder,然后让leftViewHolder和RightViewHolder继承自MsgViewHolder。这样就相当于密封类MsgViewHolder只有两个已知子类,因此在when语句中只要处理这两种情况的条件分支即可。

修改MsgAdapter代码,如下所示:

class MsgAdapter(val msg编程客栈List: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适配器的写法更加规范也更加推荐。

到此这篇关于kotlin延迟初始化和密封类详细讲解的文章就介绍到这了,更多相关Kotlin延迟初始化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号