延迟初始化
背景
Kotlin 提供了变量不可空特性来抑制空指针问题。
这个特性在保证程序安全的同时,也给我们带来了编码上的一些问题。
案例:试想一下,你的程序中有一个全局变量 a ,变量 a 创建之初并没有初始化,所以你令它等于 null。
假设 a 的类型为 String,因为要保证 a 非空,所以它的类型被声明为 String?
private var a: String? = null
接下来,每次需要调用 a 中的方法,都要进行判空处理(即使它早已被初始化),否则无法进行编译!
当代码中有越来越多的全局变量实例时,这个问题会愈发明显。
延迟初始化
为了解决这个问题,Kotlin 使用 lateinit 关键字对变量进行初始化。
private lateinit var a: String
lateinit 关键字告诉 Kotlin 编译器,我会在后续对该变量进行初始化,这样就不用将变量赋值为 null。
这样,在调用全局变量 a 中的方法时,就不用再进行判空处理。
在使用该对象时,需要保证其已被初始化,否则会出现运行时错误!
避免重复初始化
为了避免变量的重复初始化,可以使用以下代码
if (!::a.isInitialized) {
// 判断 a 是否初始化,如果未初始化执行代码块
a = "ming ming" // 对 a 进行初始化操作
}
::object.isInitialized 是判断初始化的固定写法。
密封类
背景
新建一个 Result.kt 文件,在文件中编写以下代码:
interface Result
class Success(val message:String):Result
class Failure(val error: Exception):Result
代码中定义了一个 Result 接口,用于表示某个操作的执行结果。
定义两个类去实现 Result 接口,Success 类用于表示成功时的结果,Failture 类用于表示失败时的结果。
再定义一个 getResultMessage(),用于执行最终执行结果的信息,代码如下
fun getResultMessage(result: Result) = when(result){
is Success -> result.message
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
getResultMessage() 方法接收一个 Result 参数。
我们用 when 语句来判断 Result 的类型,如果为 Success,打印成功消息,如果为 Failture 返回错误信息。
讲道理,此时应该结束代码。因为代码的执行结果要么成功,要么失败,并不存在其他情况。
但是为了满足 Kotlin 的语法检查,在最后写了一个 else 来抛出异常(实际上这个 else 是永远无法到达的)。
此外,如果我们新增了一个其他类继承 Result 接口,而忘了在 getResultMessage() 中添加对应的执行代码,
代码在执行时就会直接抛出 else 中的异常。
密封类
密封类可以很轻松地解决上述问题,声明一个密封类只需要在类前加上关键字 sealed。
将 Result 接口改造成密封类
sealed class Result
class Success(val message:String):Result()
class Failure(val error: Exception):Result()
接下来在 when 中传入密封类做为条件,Kotlin 会检查该密封类有哪些子类,并强制要求你去处理每个子类所对应的条件,这样就可以保证即使没有编写 else 条件,也不会出现漏写分支的情况。
fun getResultMessage(result: Result) = when(result){
is Success -> result.message
is Failure -> result.error.message
}
当我们新建一个 Others 类继承 Result 密封类,getResultMessage() 会报错。
我们可以通过 Add remaining branches 来添加对所有子类对应条件的处理。