4.8.1 对变量延迟初始化
在我们的应用中,有些时候需要定义一个空的全局变量,然后在后面的逻辑中去初始化这个全局变量。但是Kotlin 机制如果一个全局变量想要定义成为null ,那么需要在变量的类型后面加一个?问号,例如:
private var count:String? = null
但是在之后的代码中调用这个变量到要做一个非空判断处理才能够使用,即使我们初始化过这个变量,确定这个变量肯定会被初始化,一定不是空的,Kotlin 机制也会让我们去做判断,这样是因为Kotlin 的空指针检查语法标准所决定的。
但是Kotlin 还是给我们提供了方式,就是允许变量的延迟初始化,我们只需要在变量 var 前面添加lateinit 关键字就可以告诉Kotlin 这个变量稍后我们自己会做初始化,不需要去判断空指针了,当然前提是我们自己要确保这个变量被初始化成功,不为空,不然的话,没有初始化去直接使用它还是会导致程序崩溃,并且抛出UninitializedPropertyAccessException异常的。
private lateinit var count:String
这样的话,使用了这个关键字,我们就不需要特意的去赋 null 了。
为了避免这种异常,我们可以通过代码来判断这个变量是否已经初始化了,还可以避免重复初始化以节约内存。
if (!::count.isInitialized){
count = "init"
}
!惊叹号我们都知道是非的意思,:: 这两个符号是我们没见过的,它是Kotlin 的固定语法,被这两个冒号放在前面的变量可以调用一些内置函数,isInitialized 值代表的就是是否初始化过。
4.8.2 使用密封类优化代码
要想知道密封类的作用,首先看一个简单的例子。新建一个Result.kt 的Kotlin 文件,然后添加如下代码:
interface Result
class Success(val msg:String):Result
class Error(val error:Exception):Result
我们在文件中创建了两个类 都去实现同一个接口。根据多态的特点,那么这两个类的父类型也就是相同的。接下来我们再定义一个方法来获取我们来传入 这个接口变量 ,并且判断是哪个子类执行相对的逻辑。
interface Result
class Success(val msg:String):Result
class Error(val error:Exception):Result
fun getResultMsg(result: Result) = when(result){
is Success -> result.msg
is Error -> result.error.message
else -> throw IllegalAccessException()
}
可以看出来我们在这个getResultMsg 函数中返回了 一些信息,根据我们传入的result 类型来判断返回哪种信息,但是else 是我们不想去编写的,如果不编写就会报错,无法通过编译,Kotlin 会为这里缺少分支,添加else 只是为了编译器的语法而以,实际是永远也不会走到else 的。
这种无用分支的情况在其他语言也会有,但Kotlin 为我们提供了解决方式,就是密封类。
密封类的关键字 是sealed class 用这个关键字修饰的类就是密封类,密封类是一个可以继承的类,不需要添加open 关键字,当一些类继承了同一个密封类后,我们用when 函数去判断类型,Kotlin 编译器就会自动检查该密封类有哪些子类,并强制要求开发者将每一个子类对应的条件全部处理。
这样就可以保证即使没有写else 也不会出现漏写条件分支。编译器也就不会让我们强制去写else 了:
sealed class Result
class Success(val msg:String):Result()
class Error(val error:Exception):Result()
fun getResultMsg(result: Result) = when(result){
is Success -> result.msg
is Error -> result.error.message
}
这里需要特别注意 密封类及其所有的子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的。
我们可以优化,UIBestPractice 项目中的ViewHolder,之前我们在onBindViewHolder 回调函数中有一个when 的类型holder 类型判断,我就从这里入手(PS:不优化也没关系,只是练习)
由于密封类的特性,我们需要创建一个MsgViewHolder 的文件:
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)
}
然后再来修改MsgAdapter :
class MsgAdapter(private val msgList: List): RecyclerView.Adapter() {
// 根据viewType 来创建不同子项布局的ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MsgViewHolder = if (viewType == Msg.TYPE_RECEIVED){
LeftViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item,parent,false))
}else{
RightViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item,parent,false))
}
override fun getItemCount(): Int = msgList.size
override fun onBindViewHolder(holder: MsgViewHolder, position: Int) {
val msg = msgList[position]
when(holder){
is LeftViewHolder -> holder.itemView.leftMsg.text = msg.content
is RightViewHolder -> holder.itemView.rightMsg.text = msg.content
}
}
// 返回每个子项的type 类型 主要用来在onCreateViewHolder 函数中创建不同子项布局的ViewHolder
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
}
我们继承的RecyclerView.Adapter的泛型变成了MsgViewHolder ,onCreateViewHolder 的返回值类型也变成了 MsgViewHolder ,而且onBindViewHolder 的参数 holder 类型也变成 MsgViewHolder 这样我们的代码就规范了很多。这种写法更加推荐。