3.2.7 空安全

原文链接: https://kotlinlang.org/docs/kotlin-tour-null-safety.html

3.2.7 空安全

在 Kotlin 中,可以存在 null 值。当某个东西缺失或尚未设置时,Kotlin 会使用 null 值。在集合一章中,当你试图用一个映射中不存在的键去访问键值对时,你已经看到了 Kotlin 返回 null 值的例子。虽然以这种方式使用 null 值很有用,但如果你的代码没有准备好处理它们,就可能遇到问题。

为了帮助避免程序中 null 值带来的问题,Kotlin 引入了空安全机制。空安全会在编译期而不是运行期检测 null 值的潜在问题。

空安全是一组特性的组合,让你可以:

  • 显式声明程序中何时允许出现 null 值。
  • 检查 null 值。
  • 对可能包含 null 值的属性或函数使用安全调用。
  • 声明检测到 null 值时要执行的动作。

可空类型

Kotlin 支持可空类型,它允许所声明的类型具有 null 值。默认情况下,类型不允许接受 null 值。可空类型通过在类型声明之后显式添加 ? 来声明。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fun main() {
    // neverNull has String type
    var neverNull: String = "This can't be null"

    // Throws a compiler error
    neverNull = null

    // nullable has nullable String type
    var nullable: String? = "You can keep a null here"

    // This is OK
    nullable = null

    // By default, null values aren't accepted
    var inferredNonNull = "The compiler assumes non-nullable"

    // Throws a compiler error
    inferredNonNull = null

    // notNull doesn't accept null values
    fun strLength(notNull: String): Int {
        return notNull.length
    }

    println(strLength(neverNull)) // 18
    println(strLength(nullable))  // Throws a compiler error
}

提示: length 是 String 类的一个属性,它包含字符串中的字符数。

检查 null 值

你可以在条件表达式中检查是否存在 null 值。在下面的示例中,describeString() 函数中有一个 if 语句,它检查 maybeString 是否不为 null,以及它的 length 是否大于零:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun describeString(maybeString: String?): String {
    if (maybeString != null && maybeString.length > 0) {
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"
    }
}

fun main() {
    val nullString: String? = null
    println(describeString(nullString))
    // Empty or null string
}

使用安全调用

要安全地访问可能包含 null 值的对象属性,请使用安全调用运算符 ?.。如果对象本身或它被访问的某个属性是 null,安全调用运算符就会返回 null。如果你想避免 null 值在代码中触发错误,这会很有用。

在下面的示例中,lengthString() 函数使用安全调用,返回字符串的长度或 null:

1
2
3
4
5
6
7
fun lengthString(maybeString: String?): Int? = maybeString?.length

fun main() {
    val nullString: String? = null
    println(lengthString(nullString))
    // null
}

提示: 安全调用可以串联使用:如果对象的任何属性包含 null 值,就会返回 null,而不会抛出错误。例如:kotlin person.company?.address?.country

安全调用运算符也可以用来安全地调用扩展函数或成员函数。在这种情况下,调用函数之前会先做一次 null 检查。如果检查发现 null 值,就会跳过调用并返回 null。

在下面的示例中,nullString 是 null,因此对 .uppercase() 的调用会被跳过,并返回 null:

1
2
3
4
5
fun main() {
    val nullString: String? = null
    println(nullString?.uppercase())
    // null
}

使用 Elvis 运算符

你可以使用 Elvis 运算符 ?: 提供一个在检测到 null 值时返回的默认值。

把需要检查是否为 null 的表达式写在 Elvis 运算符左侧,把检测到 null 值时应返回的内容写在右侧。

在下面的示例中,nullString 是 null,因此访问 length 属性的安全调用返回 null 值。结果,Elvis 运算符返回 0:

1
2
3
4
5
fun main() {
    val nullString: String? = null
    println(nullString?.length ?: 0)
    // 0
}

关于 Kotlin 中空安全的更多信息,请参阅空安全。

练习

习题

你有一个 employeeById 函数,可以用来访问某家公司的员工数据库。遗憾的是,这个函数返回 Employee? 类型的值,因此结果可能是 null。你的目标是编写一个函数:当提供员工 id 时返回该员工的薪水,如果数据库中不存在该员工则返回 0。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
data class Employee (val name: String, var salary: Int)

fun employeeById(id: Int) = when(id) {
    1 -> Employee("Mary", 20)
    2 -> null
    3 -> Employee("John", 21)
    4 -> Employee("Ann", 23)
    else -> null
}

fun salaryById(id: Int) = // Write your code here

fun main() {
    println((1..5).sumOf { id -> salaryById(id) })
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
data class Employee (val name: String, var salary: Int)

fun employeeById(id: Int) = when(id) {
    1 -> Employee("Mary", 20)
    2 -> null
    3 -> Employee("John", 21)
    4 -> Employee("Ann", 23)
    else -> null
}

fun salaryById(id: Int) = employeeById(id)?.salary ?: 0

fun main() {
    println((1..5).sumOf { id -> salaryById(id) })
}

示例解答

接下来做什么?

恭喜!既然你已经完成了入门导览,就用进阶导览把你的 Kotlin 水平提升到下一个层次:

另请参阅

上一步

开始进阶 Kotlin 之旅