Kotlin 出现断言的两种情形
- IDE java 与 kotlin 自动转换时,自动添加非空断言的代码
- Smart Cast 失效
代码展示:
class JavaConvertExample {
private var name: String? = null
fun init() {
name = ""
}
fun foo() {
name = null;
}
fun test() {
if (name != null) {
// 这里去掉 !! 编译器会报错
// 非空断言
// ↓
val count = name!!.length
}
}
}
也许疑问,为什么判断非空后,还需要断言,因为比如出现如下情况
class JavaConvertExample {
private var name: String? = null
fun init() {
name = ""
}
fun foo() {
name = null;
}
fun test() {
if (name != null) {
// 几百行代码
foo()
//几百行代码
val count = name!!.length
}
}
}
逻辑变复杂后,他可能就仍然会变为空。所以,进入正题,避免使用非空断言的几种方式
第一种,避免直接访问变量或者全局变量,将其改为传参的形式:
// 改为函数参数
// ↓
fun test(name: String?) {
if (name != null) {
// 函数参数支持Smart Cast
// ↓
val count = name.length
}
}
在 kotlin 当中,函数的参数是不可变的,因此,当我们将外部的成员变量或者全局变量以函数参数的形式传进来以后,它可以用于 Smart Cast 了。
第二种,避免使用可变变量 var,改为 val 不可变变量:
class JavaConvertExample {
// 不可变变量
// ↓
private val name: String? = null
fun test() {
if (name != null) {
// 不可变变量支持Smart Cast
// ↓
val count = name.length
}
}
}
这种方式很好理解,既然引发问题的根本原因是可变性导致的,我们直接将其改为不可变的即可。
第三种,借助临时的不可变变量:
class JavaConvertExample {
private var name: String? = null
fun test() {
// 不可变变量
// ↓
val _name = name
if (_name != null) {
// 在if当中,只使用_name这个临时变量
val count = _name.length
}
}
}
第四种,借助 kotlin 提供的 标准函数 let:
class JavaConvertExample {
private var name: String? = null
fun test() {
// 标准函数
// ↓
val count = name?.let { it.length }
}
}
第五种,借助 kotlin 提供的 lateinit 关键字
class JavaConvertExample {
// 稍后初始化 不可空
// ↓ ↓
private lateinit var name: String
fun init() {
name = "Tom"
}
fun test() {
if (this::name.isInitialized) {
val count = name.length
} else {
println("Please call init() first!")
}
}
}
fun main() {
val example = JavaConvertExample()
example.init()
example.test()
}
第六种,使用 by lazy 委托:
class JavaConvertExample {
// 不可变 非空 懒加载委托
// ↓ ↓ ↓
private val name: String by lazy { init() }
fun init() = "Tom"
fun test() {
val count = name.length
}
}
可以看到,我们将 name 这个变量改为了不可变的非空属性,并且,借助 Kotlin 的懒加载委托来完成初始化。借助这种方式,我们可以尽可能地延迟初始化,同时,也消灭了可变性、可空性。
以上几种方式,可以避免我们使用非空断言
注:摘自《朱涛 · Kotlin编程第一课》