提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。
下面这段代码取自我们聊天界面实战项目中的MainActivity。我们先声明一个全局变量myAdapter,由于它的初始化工作是在onCreate()方法中进行的,所以我们只能先将myAdapter赋值为null。
虽然我们会在onCreate()方法中对myAdapter进行了初始化,同时确保onClick()方法必然在myAdapter初始化后才会调用。但是我们还是需要在调用notifyItemInserted()方法时,对myAdapter进行了判空处理,不然Kotlin是无法通过编译的。
class MainActivity : AppCompatActivity(), View.OnClickListener {
private var myAdapter: MsgAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
· · ·
//适配器的初始化
myAdapter = MsgAdapter(msgList)
· · ·
send_button.setOnClickListener(this)
}
override fun onClick(v: View?) {
· · ·
//通知RV列表有新数据插入
myAdapter?.notifyItemInserted(msgList.size - 1)
· · ·
}
}
当你的代码中有了越来越多的全局变量实例时,这个问题就会变得越来越明显,你很可能需要在代码中编写大量的判空处理才可以满足Kotlin编译器的要求。
我们可以通过"全局变量延迟初始化"这种方式解决这个问题。延迟初始化使用的是lateinit关键字,通过延迟初始化告诉Kotlin编译器:“我将在晚些时候对这个变量进行初始化”,这样就不用在一开始的时候将其赋值为null。
接下来我们就是用延迟初始化的方式对上述代码进行优化:
class MainActivity : AppCompatActivity(), View.OnClickListener {
//延迟初始化 就不用将myAdapter赋值为null了
private lateinit var myAdapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
· · ·
//适配器的初始化
myAdapter = MsgAdapter(msgList)
· · ·
send_button.setOnClickListener(this)
}
override fun onClick(v: View?) {
· · ·
//通知RV列表有新数据插入
myAdapter.notifyItemInserted(msgList.size - 1)
· · ·
}
}
可以看到我们在myAdapter变量前添加了一个lateinit关键字,这样就不用在一开始的时候将它赋值为null,同时类型声明也就可以改成MsgAdapter了,而不用加上问号。由于MsgAdapter是不可为null的类型,所以我们在onClick()方法中也就不再需要进行判空处理,而是直接调用myAdapter的notifyItemInserted()方法。
需要注意的是:当你对一个全局变量使用lateinit关键字后,必须要确保它在任何地方调用之前已经完成了初始化工作。
此外我们还可以通过代码来判断一个全局变量是否已经完成了初始化操作:
class MainActivity : AppCompatActivity(), View.OnClickListener {
//延迟初始化
private lateinit var myAdapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
· · ·
//如果myAdapter没有初始化则立马进行初始化
if (!::myAdapter.isInitialized) {
myAdapter = MsgAdapter(msgList)
}
· · ·
send_button.setOnClickListener(this)
}
· · ·
}
判断一个变量是否初始化的语法是 ::myAdapter.isInitialized ,可用于判断myAdapter变量是否已经初始化。::用来引用特定类的静态方法或属性的语法,这是一种固定的写法。然后我们再对结果进行取反,如果myAdapter还没有初始化,就立即对myAdapter进行初始化,否则就什么都不做。
密封类可以结合RecyclerView适配器中的ViewHolder一起使用。
首先我们先来学习一下如何使用密封类,这里新建一个名为Result.kt的Kotlin文件:
//Result接口
interface Result {}
//两个实现了Result接口的类
class Success(val msg: String) : Result {}
class Failure(val error: Exception) : Result {}
然后我们再定义一个getResultMsg()方法,用来获取最终执行的结果:
fun getResultMsg(result: Result) {
when (result) {
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
}
这里getResultMsg()方法接收一个Result型参数,然后在方法体内通过when语句进行判断:如果Result属于Success就返回成功的消息,如果属于Failure就返回错误信息。但是我们不得不再编写一个else语句,不然Kotlin编译器无法通过编译。实际上我们Result的执行结果只可能是Success或者Failure,这个else条件是永远也走不到的。
其实还有很多风险,例如我们新声明一个Unknown类,让它同样实现Result接口。如果我们忘记在getResultMsg()方法中增加逻辑判断,那么运行的时候会进入到else条件里面,从而抛出异常。
在Kotlin中我们可以通过密封类来解决“为了满足编译器的要求而编写无用条件分支代码”。密封类的关键字是sealed class,下面的代码中我们将Result改造成密封类方法:
//密封类Result
sealed class Result {}
//两个实现了Result接口的类
class Success(val msg: String) : Result() {}
class Failure(val error: Exception) : Result() {}
我们在上面的代码中只是将interface关键字换成了sealed class,由于密封类可以被继承,所以在继承它的时候需要在后面加上一对括号。
子类的主构造函数在初始化的时候会调用父类的无参构造函数。
在将Result接口改造成密封类后,我们就不需要在getResultMsg()方法中编写else条件语句了:
fun getResultMsg(result: Result) = when (result) {
is Success -> result.msg
is Failure -> “Error is ${result.error.message}”
}
当你往when语句中传入一个密封类对象作为判断条件时,Kotlin编译器会自动检查该密封类都有哪些子类,并强制要求我们将每一个子类所对应的条件全部处理。这时如果我们新声明一个Unknown类,让它实现Result接口。此时getResultMsg()方法是会报错的,我们必须在getResultMsg()方法中为Unknown类增加条件分支才可以编译通过。
密封类及其所有子类必须定义在同一个文件的顶层位置,不能嵌套在其他类中。
接下来我们看一下第9篇文章中的RecyclerView适配器——MsgAdapter:
class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
//内部类ViewHolder用于缓存列表项控件
inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val leftMsg: TextView = view.findViewById(R.id.leftMsg)
}
//发送消息的ViewHolder 缓存msg_right_item布局中的控件
inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}
//获取当前position的消息类型
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
viewType的值由getItemViewType决定
|
V
//用于创建ViewHolder对象
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
//根据不同的viewType创建不同的界面
return if (viewType == Msg.TYPE_RECEIVED) {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
LeftViewHolder(view)
} else {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
RightViewHolder(view)
}
}
//用于对RecyclerView列表项进行赋值
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val msg = msgList[position]
when (holder) {
is LeftViewHolder -> holder.leftMsg.text = msg.content
is RightViewHolder -> holder.rightMsg.text = msg.content
else -> throw IllegalArgumentException()
}
}
//用于告诉RecyclerView一共有多少列表项
override fun getItemCount(): Int {
return msgList.size
}
}
在onBindViewHolder()这段代码中,when语句中也有一个else条件判断语句,它的作用仅仅是抛出异常。对于这部分代码,我们可以通过密封类的特性进行优化。首先我们删除MsgAdapter中的LeftViewHolder和RightViewHolder这两个内部类,然后新建一个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。这样就相当于密封类有两个已知的子类,此时在when语句中只需要处理这两种情况的分支就可以了。修改MsgAdapter中的代码:
MsgViewHolder
class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<MsgViewHolder>() {
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
}
}
}
这里我们将RecyclerView.Adapter的泛型指定为密封类MsgViewHolder,然后在onBindViewHolder()方法中将holder类型设置为MsgViewHolder就可以了。这样我们就不需要在编写else分支判断逻辑了!