# Kotlin教程 - 4 非空安全
null
值是一个特殊的值,表示一个变量或常量 没有值
,是空的值。当一个变量是 null,我们使用这个变量进行操作的时候,在 Java 中就会报空指针异常(NullPointerException),Kotlin 针对这种情况进行了改良,提前在编译期间进行了相关的限制。
# 4.1 可空类型和非空类型
在 Java 中声明了一个变量赋空值是正常的。但是在 Kotlin中,所有的数据类型默认是非空的。
我们在前面将字符串转换为整数的时候,使用的是如下代码:
fun main() {
val str = "123"
val value1 = str.toInt()
val value2 = str.toIntOrNull()
println(value1)
println(value2)
}
2
3
4
5
6
7
8
我们在转换的时候,是没有添加类型的,如果添加上类型的话,会发现有问题:
fun main() {
val str = "123"
val value1:Int = str.toInt()
val value2:Int = str.toIntOrNull() // 这句代码会报错
println(value1)
println(value2)
}
2
3
4
5
6
7
8
上面的代码 val value2:Int = str.toIntOrNull()
会报错,因为如果 str
是不能转换为数字的字符串,结果将是 null
,而 val value2:Int
是不能被赋空值的,所以需要将 val value2:Int
修改为 val value2:Int?
表示 value2
可以为空值。
而因为 toInt()
在转换的时候,如果字符串不能被转换为数字,会报错,程序会终止,所以只要程序正常,value1
必定不会为空。
默认情况下,Kotlin 中所有类型都是非空类型,因此变量、常量默认不能为
null
。如果想要使用可空类型,需要使用
?
来标记一个变量、常量为可空类型。
再举个栗子:
fun main() {
var value1: String? = null // 可以赋空值
var value2: String = null // 报错,非空类型,不可以赋空值
value1 = "Doubi"
println(value1) // 输出:Doubi
}
2
3
4
5
6
7
所以添加 ?
表示可空,不添加 ?
默认是非空类型。
# 4.2 类型提升
为了保证空安全特性,Kotlin 的流分析(flow analysis)已经考虑了空特性。如果一个可空对象不可能有空值,那么就会被当作非空对象处理。
举个栗子:
下面的代码编译是会报错的。
fun main() {
var str: String? = null
println(str.length) // 编译出错,str为null
}
2
3
4
但是如果我们对str进行非空判断,则确保了str不会为空,编译就不会报错了:
fun main() {
var str: String? = null
if (null != str) {
print(str.length) // 已经确保 str 不为空,不会编译出错
}
}
2
3
4
5
6
# 4.3 空安全调用
在Kotlin中,使用 ?.
(空安全调用操作符)来进行空安全调用。空安全调用表示如果这个变量是空值,则不会调用它的函数或者访问它的属性。
fun main() {
var str: String? = null
var length:Int? = str?.length
println(length) // 输出: null
}
2
3
4
5
在上面的代码中,虽然 str 为 null
,但是使用了空安全调用,所以不会报错。
使用 if-else
也可以实现效果,为什么要使用空安全调用操作符呢?
一个是更简洁,二是空安全调用操作符可以进行多个函数的链式调用。
举个栗子:
data class Person(val name: String?)
fun main() {
val person: Person? = Person("Doubi")
// 使用if判断
if (null != person) {
if (null != person.name) {
val length = person.name.length
println(length)
}
}
// 使用空安全调用操作符,可以链式调用
val length = person?.name?.length
println(length)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.4 let函数
很多情况下,空安全调用 ?.
会结合 let
函数来使用。
let
是 Kotlin 标准库中的一个函数,它用于执行一个作用域函数(scope function),并将调用者作为 lambda 表达式的参数传递给这个函数。let
的主要目的是在对象上执行某些操作,并且可以方便地处理 null
值。
举个栗子:
fun main() {
val name:String? = "Doubi"
val content: String? = name?.let { // let是一个lambda表达式,参数是调用者本身
println(it.length)
"Hello ${it}"
}
println(content)
}
2
3
4
5
6
7
8
9
let
是一个lambda表达式,参数是调用者本身,也就是 it
就是 name
。
name?.let {}
表示如果name 不为空就执行 lambda 代码块中的内容,并将 lambda 代码块最后一行内容返回作为结果。
name?.let {}
有点像 if
判断 name
是否为空,如果不为空就执行操作,例如:
fun main() {
val name:String? = "Doubi"
var name2:String? = null
name?.let { // name 不为空就执行操作,将name赋值给name2
name2 = name
}
println(name2)
}
2
3
4
5
6
7
8
9
let 函数和 Lambda 表达式后面在讲解,如果这里不清楚,不必慌张。
# 4.5 非空断言
在Kotlin中,使用 !!
来进行非空断言。非空断言表示这个变量一定不是空值。
举个栗子:
fun main() {
var str: String? = null
print(str!!.length)
}
2
3
4
虽然 str
是空的,但是使用了 !!
来告诉编译器,str
一定不为空,所以才通过编译。但是上面的代码运行会报错。
所以使用非空断言的时候,一定要确保变量一定不为空,如果它是空值则会抛出异常。
使用非空断言,可以将可空类型转换为非空类型。
fun main() {
var str1: String? = "abc"
var str2: String = str1!! // 需要非空断言才能赋值
}
2
3
4
可空类型
是不能直接赋值给 非空类型
的,所以在上面的代码中,需要使用非空断言,将可空类型转换为非空类型才能赋值。
当然使用之前的类型提升也是可以的:
fun main() {
var str1: String? = "abc"
if (null != str1) {
var str2: String = str1 // str1已经做了类型提示,可以赋值
}
}
2
3
4
5
6
7
# 4.6 Elvis 运算符?:
Elvis 运算符 ?:
也叫空合并操作符
,用于判断一个表达式是否为null
,如果表达式结果不为 null
,则返回左侧表达式的结果,否则返回右侧表达式的结果,和有的语言的三元运算符有些类似。
举个栗子:
fun main() {
var name:String? = null
var fullName = name ?: "Doubi"
print(fullName) // 输出:Doubi
}
2
3
4
5
上面 var fullName = name ?: "Doubi"
表示先判断 name
是否为 null
,如果不为 null
,则将 name
的值赋值给 fullName
,否则将"Doubi"
赋值给 fullName
。
空合并操作符还经常结合let函数来使用,举个栗子:
fun main() {
var str:String? = "doubi"
str = str?.let { it.capitalize() } ?: "Niubi"
println(str)
}
2
3
4
5
6
在上面的代码中,首先判断 str
是否为空,如果不为空,就在 let
函数中将字符串首字母变成大写,如果 str
为空,就将 "Niubi"
赋值给 str
。