空安全
Kotlin官网:Other-Null Safety
可空类型和不可空类型
Kotlin中将类型分为可空和不可空,以尽量避免空指针(NPE-NullPointerException)的问题。只有在如下情况下才会发生空指针异常:
- 主动抛出
throw NullPointerException()
; !!
运算符;- 被认为已经初始化,实际没有:
- 构造函数中将还未初始化完的this赋值给其他地方使用;
- 父类中使用了子类实现的成员,没有正确初始化;
- 和Java交互:
- 调用了Java中的null对象;
- 非空泛型被从Java中放入了null值;
- 其他Java中引起的空指针。
Kotlin中区分可空和不可空,例如一个不可空的String:
var a: String = "abc"
a = null // 编译报错
要使用可空类型,声明为可空字符串String?
:
var b: String? = "abc"
b = null // ok
此时调用不可空类型a的成员,Kotlin可以确保不会出现NPE,可以直接调用:
val l = a.length
如果访问可空类型b的成员,由于不保证非空,直接调用编译器会报错:
val l = b.length // error: variable 'b' can be null
非空检查
可以检查b是否为null,根据两种情况分别处理:
val l = if (b != null) b.length else -1
更复杂的也可以:
if (b != null && b.length > 0) {
print("String of length ${b.length}")
} else {
print("Empty string")
}
这种检查后调用只有在b检查后不变才生效,例如一个局部变量,非空检查后立刻调用,或是一个只读的不能被重写的成员。这是因为如果是外部可更改的话,可能会在非空检查后被修改为空值。
安全调用
其次可以使用安全调用运算符?
:
b?.length
在b不为null时返回b.length,为空是返回null,所以整个表达式的类型是Int?。
安全调用可以写成链式,链式调用的任意节点为空都会中断调用并返回null:
bob?.department?.head?.name
如果要在?调用中一次执行多步,可以用let函数:
val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // prints A and ignores null
}
非空调用也可以写在赋值语句左侧,这样如果要赋值的链节中有空值则中断,表达式右侧不会被调用:
// If either `person` or `person.department` is null, the function is not called:
person?.department?.head = managersPool.getManager()
Elvis运算符
假如有一个可空类型的引用r,希望在r不为空时使用,如果为空则使用默认值:
val l: Int = if (b != null) b.length else -1
对于这种if表达式的形式,有更简便的Elivis运算符?:
:
val l = b?.length ?: -1
对于Elvis运算符,如果?:
左侧的值不为null,则返回此值,如果为null则返回表达式右侧的值。
?:
右侧表达式只在左侧表达式的值为null时才会执行。
Kotlin中throw
和return
都是表达式,可以用于Elvis运算符右侧:
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ...
}
!!
运算符
第三种处理方式针对惯用NPE的用户。非空断言运算符!!
可以将任何类型转换成不可空类型,如果为空则抛出NPE:
val l = b!!.length
安全类型转换
类型转换如果转换目标类型不符会抛出ClassCastException
。使用安全类型转换可以在转换失败时返回null值:
val aInt: Int? = a as? Int
可空类型集合
filterNotNull
函数可以将可空类型集合的非空元素筛选成一个不可空类型集合:
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()