1. 开始之前
本 Codelab 教您了解可空性以及 null
安全的重要性。可空性是许多编程语言中常见的概念。它指的是变量能够表示值的缺失。在 Kotlin 中,为了实现 null
安全,可空性得到了特殊处理。
前提条件
- 具备 Kotlin 编程基础知识,包括变量、从变量访问方法和属性,以及
println()
和main()
函数。 - 熟悉 Kotlin 条件语句,包括
if/else
语句和布尔表达式。
学习内容
- 什么是
null
。 - 可空类型和非可空类型之间的区别。
null
安全是什么、它的重要性以及 Kotlin 如何实现null
安全。- 如何使用
?.
安全调用运算符和!!
非空断言运算符访问可空变量的方法和属性。 - 如何使用
if/else
条件语句执行null
检查。 - 如何使用
if/else
表达式将可空变量转换为非可空类型。 - 如何在使用
if/else
表达式或?:
Elvis 运算符时,在可空变量为null
时提供默认值。
所需物品
- 可访问 Kotlin Playground 的网络浏览器。
2. 使用可空变量
什么是 null
?
在第 1 单元中,您学习到声明变量时,需要立即为其赋值。例如,当您声明一个 favoriteActor
变量时,您可能会立即为其赋值字符串值 "Sandra Oh"
。
val favoriteActor = "Sandra Oh"
但是,如果您没有最喜欢的演员怎么办?您可能希望为变量赋 "Nobody"
或 "None"
的值。这种方法不好,因为您的程序会将 favoriteActor
变量解释为具有 "Nobody"
或 "None"
的值,而不是完全没有值。在 Kotlin 中,您可以使用 null
来指示没有与该变量关联的值。
要在代码中使用 null
,请执行以下步骤:
- 在Kotlin Playground 中,将
main()
函数主体中的内容替换为将favoriteActor
变量设置为null
的代码。
fun main() {
val favoriteActor = null
}
- 使用
println()
函数打印favoriteActor
变量的值,然后运行该程序。
fun main() {
val favoriteActor = null
println(favoriteActor)
}
输出类似于以下代码段:
null
使用 null
重新赋值变量
之前,您了解到可以使用 var
关键字定义的变量重新赋值为同一类型的不同值。例如,只要新名称是 String
类型,就可以将声明时带有一个名称的 name
变量重新赋值为另一个名称。
var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"
声明变量后,有时您可能希望将变量赋值为 null
。例如,在声明了您最喜欢的演员之后,您决定完全不想透露您最喜欢的演员。在这种情况下,将 favoriteActor
变量赋值为 null
就很有用。
了解非可空变量和可空变量
要将 favoriteActor
变量重新赋值为 null
,请执行以下步骤:
- 将
val
关键字更改为var
关键字,然后指定favoriteActor
变量的类型为String
,并将其赋值为您最喜欢的演员的名称。
fun main() {
var favoriteActor: String = "Sandra Oh"
println(favoriteActor)
}
- 移除
println()
函数。
fun main() {
var favoriteActor: String = "Sandra Oh"
}
- 将
favoriteActor
变量重新赋值为null
,然后运行该程序。
fun main() {
var favoriteActor: String = "Sandra Oh"
favoriteActor = null
}
您会收到此错误消息:
在 Kotlin 中,可空类型和非可空类型之间存在区别:
- 可空类型是指_可以_存储
null
的变量。 - 非可空类型是指_不能_存储
null
的变量。
只有在您明确允许它存储 null
时,一个类型才是可空的。正如错误消息所示,String
数据类型是一个非可空类型,因此您无法将变量重新赋值为 null
。
要在 Kotlin 中声明可空变量,需要在类型末尾添加一个 ?
运算符。例如,String?
类型可以存储字符串或 null
,而 String
类型只能存储字符串。要声明可空变量,您需要明确添加可空类型。如果没有可空类型,Kotlin 编译器会推断它是一个非可空类型。
- 将
favoriteActor
变量的类型从String
数据类型更改为String?
数据类型。
fun main() {
var favoriteActor: String? = "Sandra Oh"
favoriteActor = null
}
- 在
null
重新赋值之前和之后打印favoriteActor
变量,然后运行该程序。
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor)
favoriteActor = null
println(favoriteActor)
}
输出类似于以下代码段:
Sandra Oh null
favoriteActor
变量最初存储一个字符串,然后被重新赋值为 null
。
试一试
现在您可以使用可空类型 String?
了,您能否初始化一个变量为 Int
值,然后将其重新赋值为 null
?
编写一个可空的 Int
值
- 移除
main()
函数中的所有代码。
fun main() {
}
- 创建一个可空
Int
类型的number
变量,然后将其赋值为10
。
fun main() {
var number: Int? = 10
}
- 打印
number
变量,然后运行该程序。
fun main() {
var number: Int? = 10
println(number)
}
输出符合预期。
10
- 将
number
变量重新赋值为null
,以确认该变量是可空的。
fun main() {
var number: Int? = 10
println(number)
number = null
}
- 在程序的最后一行添加另一个
println(number)
语句,然后运行它。
fun main() {
var number: Int? = 10
println(number)
number = null
println(number)
}
输出符合预期。
10 null
3. 处理可空变量
之前,您学习了使用 .
运算符访问非可空变量的方法和属性。在本节中,您将学习如何使用它来访问可空变量的方法和属性。
要访问非可空 favoriteActor
变量的属性,请执行以下步骤:
- 移除
main()
函数中的所有代码,然后声明一个String
类型的favoriteActor
变量,并将其赋值为您最喜欢的演员的名称。
fun main() {
var favoriteActor: String = "Sandra Oh"
}
- 使用
length
属性打印favoriteActor
变量值的字符数,然后运行该程序。
fun main() {
var favoriteActor: String = "Sandra Oh"
println(favoriteActor.length)
}
输出符合预期。
9
favoriteActor
变量的值中有九个字符,包括空格。您最喜欢的演员名称中的字符数可能不同。
访问可空变量的属性
假设您希望将 favoriteActor
变量设置为可空,以便没有最喜欢的演员的人可以将变量赋值为 null
。
要访问可空 favoriteActor
变量的属性,请执行以下步骤:
- 将
favoriteActor
变量的类型更改为可空类型,然后运行该程序。
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor.length)
}
您会收到此错误消息:
此错误是编译错误。如之前的 Codelab 所述,当 Kotlin 无法编译代码时(由于代码中的语法错误),就会发生编译错误。
Kotlin 有意应用语法规则,以实现 null
安全,这指的是_不会意外调用可能为 null
的变量_的保证。这并不意味着变量不能为 null
。它意味着如果访问了变量的成员,则变量不能为 null
。
这一点至关重要,因为如果在应用运行时试图访问一个为 null
的变量的成员(称为 null
引用),应用会崩溃,因为 null
变量不包含任何属性或方法。这种类型的崩溃称为运行时错误,错误发生在代码编译并运行之后。
由于 Kotlin 的 null
安全特性,可以防止此类运行时错误,因为 Kotlin 编译器会强制对可空类型进行 null
检查。Null
检查指的是在访问变量并将其视为非可空类型之前,检查变量是否可能为 null
的过程。如果您希望将可空值用作其非可空类型,则需要明确执行 null
检查。您将在本 Codelab 后面的使用 if/else
条件语句一节中学习此内容。
在此示例中,代码在编译时失败,因为不允许直接引用 favoriteActor
变量的 length
属性,因为变量可能为 null
。
接下来,您将学习处理可空类型的各种技巧和运算符。
使用 ?.
安全调用运算符
您可以使用 ?.
安全调用运算符访问可空变量的方法或属性。
要使用 ?.
安全调用运算符访问方法或属性,请在变量名称后添加一个 ?
符号,然后使用 .
符号访问方法或属性。
?.
安全调用运算符允许更安全地访问可空变量,因为 Kotlin 编译器会阻止任何对 null
引用的成员访问尝试,并为访问的成员返回 null
。
要安全地访问可空 favoriteActor
变量的属性,请执行以下步骤:
- 在
println()
语句中,将.
运算符替换为?.
安全调用运算符。
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor?.length)
}
- 运行此程序,然后验证输出是否符合预期。
9
您最喜欢的演员名称的字符数可能不同。
- 将
favoriteActor
变量重新赋值为null
,然后运行该程序。
fun main() {
var favoriteActor: String? = null
println(favoriteActor?.length)
}
您会看到此输出:
null
请注意,尽管试图访问 null
变量的 length
属性,程序并未崩溃。安全调用表达式只是返回了 null
。
使用 !!
非空断言运算符
您还可以使用 !!
非空断言运算符访问可空变量的方法或属性。
在可空变量之后,需要添加 !!
非空断言运算符,然后跟着 .
运算符,再跟着方法或属性,中间不要有任何空格。
正如名称所示,如果您使用 !!
非空断言,这意味着您断言变量的值不为 null
,无论它实际是否为 null
。
与 ?.
安全调用运算符不同,如果可空变量确实为 null
,使用 !!
非空断言运算符可能会导致抛出 NullPointerException
错误。因此,只有在确定变量始终不为 null
或已设置适当的异常处理时才应使用它。未处理的异常会导致运行时错误。您将在本课程后续单元中学习异常处理。
要使用 !!
非空断言运算符访问 favoriteActor
变量的属性,请执行以下步骤:
- 将
favoriteActor
变量重新赋值为您最喜欢的演员的名称,然后在println()
语句中将?.
安全调用运算符替换为!!
非空断言运算符。
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor!!.length)
}
- 运行此程序,然后验证输出是否符合预期。
9
您最喜欢的演员名称的字符数可能不同。
- 将
favoriteActor
变量重新赋值为null
,然后运行该程序。
fun main() {
var favoriteActor: String? = null
println(favoriteActor!!.length)
}
您收到 NullPointerException
错误。
这个 Kotlin 错误表明您的程序在执行期间崩溃了。因此,除非您确定变量不为 null
,否则不建议使用 !!
非空断言运算符。
使用 if/else
条件语句
您可以在 if/else
条件语句中使用 if
分支来执行 null
检查。
要执行 null
检查,您可以使用 !=
比较运算符检查可空变量是否不等于 null
。
if/else
语句
一个 if/else
语句可以与 null
检查结合使用,如下所示:
当与 if/else
语句结合使用时,null 检查非常有用。
nullableVariable != null
表达式的null
检查用作if
条件。if
分支内的 Body 1 假设变量不为null
。因此,在此主体中,您可以自由地访问变量的方法或属性,就像它是一个非可空变量一样,无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的 Body 2 假设变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。else
分支是可选的。您可以使用if
条件语句仅执行null
检查,而无需在null
检查失败时提供默认操作。
当有使用可空变量的多行代码时,null
检查与 if
条件结合使用更加方便。相反,?.
安全调用运算符更适合对可空变量进行单次引用。
要为 favoriteActor
变量编写一个带有 null
检查的 if/else
语句,请执行以下步骤:
- 再次将
favoriteActor
变量赋值为您最喜欢的演员的名称,然后移除println()
语句。
fun main() {
var favoriteActor: String? = "Sandra Oh"
}
- 添加一个带有
favoriteActor != null
条件的if
分支。
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
}
}
- 在
if
分支的主体中,添加一个println
语句,接受字符串"The number of characters in your favorite actor's name is ${favoriteActor.length}."
,然后运行该程序。
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
}
}
输出符合预期。
The number of characters in your favorite actor's name is 9.
您最喜欢的演员名称的字符数可能不同。
请注意,您可以直接使用 .
运算符访问名称的 length 方法,因为您在 if
分支内、进行 null
检查后访问 length
方法。因此,Kotlin 编译器知道 favoriteActor
变量不可能为 null
,所以编译器允许直接访问该属性。
- 可选:添加一个
else
分支来处理演员名称为null
的情况。
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
} else {
}
}
- 在
else
分支的主体中,添加一个println
语句,接受字符串"You didn't input a name."
。
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
} else {
println("You didn't input a name.")
}
}
- 将
favoriteActor
变量赋值为null
,然后运行该程序。
fun main() {
var favoriteActor: String? = null
if(favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
} else {
println("You didn't input a name.")
}
}
输出符合预期。
You didn't input a name.
if/else
表达式
您还可以将 null
检查与 if/else
表达式结合使用,以将可空变量转换为非可空变量。
将 if/else
表达式赋值给非可空类型:
nullableVariable != null
null
检查用作if
条件。if
分支内的 Body 1 假设变量不为null
。因此,在此主体中,您可以访问变量的方法或属性,就像它是一个非可空变量一样,无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的 Body 2 假设变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。- 在 Body 1 和 Body 2 的最后一行,您需要使用一个表达式或值,其结果为非可空类型,以便在
null
检查通过或失败时分别将其赋值给非可空变量。
要使用 if/else
表达式重写程序,使其仅使用一个 println
语句,请执行以下步骤:
- 将
favoriteActor
变量赋值为您最喜欢的演员的名称。
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
} else {
println("You didn't input a name.")
}
}
- 创建一个
lengthOfName
变量,然后将其赋值给if/else
表达式。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
} else {
println("You didn't input a name.")
}
}
- 移除
if
和else
分支中的两个println()
语句。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
} else {
}
}
- 在
if
分支的主体中,添加favoriteActor.length
表达式。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
favoriteActor.length
} else {
}
}
直接使用 .
运算符访问 favoriteActor
变量的 length
属性。
- 在
else
分支的主体中,添加一个0
值。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
favoriteActor.length
} else {
0
}
}
值 0
用作名称为 null
时的默认值。
- 在
main()
函数末尾,添加一个println
语句,接受字符串"The number of characters in your favorite actor's name is $lengthOfName."
,然后运行该程序。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
favoriteActor.length
} else {
0
}
println("The number of characters in your favorite actor's name is $lengthOfName.")
}
输出符合预期。
The number of characters in your favorite actor's name is 9.
您使用的名称的字符数可能不同。
使用 ?:
Elvis 运算符
?:
Elvis 运算符是一个可以与 ?.
安全调用运算符一起使用的运算符。通过 ?:
Elvis 运算符,当 ?.
安全调用运算符返回 null
时,您可以添加一个默认值。它类似于 if/else
表达式,但方式更加简洁。
如果变量不为 null
,则执行 ?:
Elvis 运算符之前的表达式。如果变量为 null
,则执行 ?:
Elvis 运算符之后的表达式。
要修改您的先前程序以使用 ?:
Elvis 运算符,请执行以下步骤:
- 移除
if/else
条件语句,然后将lengthOfName
变量设置为可空favoriteActor
变量,并使用?.
安全调用运算符调用其length
属性。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = favoriteActor?.length
println("The number of characters in your favorite actor's name is $lengthOfName.")
}
- 在
length
属性之后,添加?:
Elvis 运算符,后跟值0
,然后运行该程序。
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = favoriteActor?.length ?: 0
println("The number of characters in your favorite actor's name is $lengthOfName.")
}
输出与之前的输出相同。
The number of characters in your favorite actor's name is 9.
4. 总结
恭喜!您了解了可空性以及如何使用各种运算符进行管理。
概要
- 变量可以设置为
null
以表示它不持有任何值。 - 非可空变量不能被赋值为
null
。 - 可空变量可以被赋值为
null
。 - 要访问可空变量的方法或属性,需要使用
?.
安全调用运算符或!!
非空断言运算符。 - 您可以使用带有
null
检查的if/else
语句,在非可空上下文访问可空变量。 - 您可以使用
if/else
表达式将可空变量转换为非可空类型。 - 您可以使用
if/else
表达式或?:
Elvis 运算符为可空变量为null
时提供默认值。