空安全
Kotlin 空安全的目标是把 NPE 从运行时尽可能提前到编译期来发现,最核心的思路就是在数据类型上强制区分「可空类型」和「非空类型」,强制要求在编译期处理可能存在的 null。
数据类型
数据类型默认是非空,需要为可空类型时,在类型后加?。
lateinit 与 by lazy
变量需要初始化的时候,可以用 lateinit 和 by lazy,用法上有些差异
| 特性 | lateinit |
by lazy |
|---|---|---|
| 适用类型 | 非空引用类型 | 任意 |
| 可变性 | var |
val |
| 初始化 | 手动 | 首次访问 |
| 线程安全 | 否 | 可配置 |
| 可重置 | 否 | 否 |
安全调用操作符 ?.
如果调用的对象是 null,返回值也是 null,用来简化 if else 的调用。尤其在链式调用中,任何一个对象是 null,返回结果都是null。
Elvis 操作符 ?:
类似于 js 中的 || ,当左边的值为空时,返回右边的值或者执行右边的表达式。也可以与 return、throw结合起来用。
fun process(name: String?) {
val n = name ?: return
val m = name ?: throw IllegalArgumentException("name is null")
}
非空断言 !!
断言一个表达式非空,如果为空会抛 KotlinNullPointerException 异常。
只有当确定非空的时候才使用,否则优先用?.、?:、requireNotNull。
requireNotNull、checkNotNull
会抛出不同的异常。
fun process(name: String?) {
val n = requireNotNull(name) { "name is null" } // 抛 IllegalArgumentException
val m = checkNotNull(name) { "name is null" } // 抛 IllegalStateException
}
对比 !! 可以自定义异常信息。
安全的类型转换 as?
当类型不匹配转换失败时,直接返回 null,如果直接用 as,则会抛异常。
可空类型扩展函数
给可空类型定义扩展函数时,可以在函数对 null 进行处理,比如:
let、run、also、apply 处理可空
调用时加一个安全操作符?
val name: String? = "Alice"
// let:非空时执行
name?.let { println(it.length) }
// run:非空时执行并返回结果
val length = name?.run { this.length } ?: 0
// also:非空时执行副作用
name?.also { println("name: $it") }
when表达式
when 表达式可以直接判断 null,而不是像 Java 中的 switch 一样抛出异常。
平台类型 T! 和 Java 的互操作
Java 中的类型在 Kotlin 中无法确定是否可空,用 T! 表示。比较安全的做法是,对没有 NonNull 注解的类型,都当做可空类型来处理。
智能转换
Kotlin 中在类型已经明确为不为空时,可以将可空类型智能转换非空类型:
支持:- if (x != null)
- x?.let { }
- when (x) { is Type -> }
- requireNotNull(x)
不支持:
- var 属性(可能被其他线程修改)。
- 自定义 getter 属性。
集合与可空元素
集合中的泛型可以是非空类型也可以是可空类型,可以用 list.filterNotNull() 将集合处理成元素都是非空的。
List<String?>: 集合非空,集合中的元素可空List<String>?: 集合可空,集合中的元素非空List<String?>?: 集合可空,集合中的元素可空
泛型中的空安全
泛型中的T 默认非空,T? 是可空。
反射中的空安全
val kClass = String::class
println(kClass.isMarkedNullable) // false
val nullableClass = String::class.createType(nullable = true)
println(nullableClass.isMarkedNullable) // true
参考
- 空安全.Kotlin 官方文档 中文版