1. 准备工作
在本 Codelab 中,您将了解“可为 null 性”以及 null
安全的重要性。“可为 null 性”是许多编程语言中的常见概念,代表您可对变量不设置任何值。在 Kotlin 中,系统会刻意处理可为 null 性,以实现 null
安全。
前提条件
- 了解 Kotlin 编程基础知识,包括变量、
println()
和main()
函数以及如何访问变量的方法和属性 - 熟悉 Kotlin 条件条件,包括
if/else
语句和布尔值表达式
学习内容
- 什么是
null
? - 可为 null 类型与不可为 null 类型之间的区别
- 什么是
null
安全、其重要性如何以及 Kotlin 如何实现null
安全。 - 如何使用
?.
安全调用运算符和!!
非 null 断言运算符访问可为 null 变量的方法和属性。 - 如何使用
if/else
条件执行null
检查。 - 如何使用
if/else
表达式将可为 null 的变量转换为不可为 null 类型。 - 如何使用
if/else
表达式或?:
Elvis 运算符,在可为 null 的变量为null
时提供默认值。
所需条件
- 一个能够访问 Kotlin 园地的网络浏览器
2. 使用可为 null 的变量
什么是 null
?
在第 1 单元中,我们已经了解到,在声明变量时,需要立即为其赋值。例如,在声明 favoriteActor
变量时,可以立即为其赋予 "Sandra Oh"
字符串值。
val favoriteActor = "Sandra Oh"
但要是您没有喜爱的演员,该怎么办?您可能想为变量赋予 "Nobody"
或 "None"
值。这并不是一个好方法,因为程序会将 favoriteActor
变量解读为具有 "Nobody"
或 "None"
值,而不是根本没有值。在 Kotlin 中,我们可以使用 null
来表示变量没有任何关联的值。
如需在代码中使用 null
,请按以下步骤操作:
- 在 Kotlin 园地中,将
main()
函数主体中的内容替换成设为null
的favoriteActor
变量:
fun main() {
val favoriteActor = null
}
- 使用
println()
函数输出favoriteActor
变量的值,然后运行此程序:
fun main() {
val favoriteActor = null
println(favoriteActor)
}
输出如以下代码段所示:
null
为变量重新赋予 null
在前面的课程中,我们已了解可以为使用 var
关键字定义的变量重新赋予相同类型的不同值。例如,您可以为以某个名称声明的 name
变量重新赋予另一个名称,只要新名称为 String
类型即可。
var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"
有时,在声明某个变量后,我们可能需要为该变量赋予 null
。例如,在声明喜爱的演员后,您发现自己根本不想透露喜爱的演员。在这种情况下,为 favoriteActor
变量赋予 null
会很有用。
了解不可为 null 的变量和可为 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 类型是指可以存储
null
值的变量。 - 不可为 null 类型是指不能存储
null
值的变量。
只有当您明确让某个变量可以存储 null
值时,该变量才属于可为 null 类型。正如错误消息所示,String
数据属于不可为 null 类型,因此您无法为该变量重新赋予 null
。
如需在 Kotlin 中声明可为 null 的变量,您需要在相应类型的末尾添加 ?
运算符。例如,String?
类型可以存储字符串或 null
,而 String
类型只能存储字符串。如需声明某个可为 null 的变量,您需要明确添加可为 null 类型。如果没有可为 null 类型,Kotlin 编译器会推断该变量属于不可为 null 类型。
- 将
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
值。
试试看
现在,您可以使用可为 null String?
类型了。那么您可以使用 Int
值来初始化变量并为其重新赋予 null
吗?
写入可为 null 的 Int
值
- 移除
main()
函数中的所有代码:
fun main() {
}
- 创建一个
number
变量,其中含有可为 null 的Int
类型,然后为该变量赋予10
值:
fun main() {
var number: Int? = 10
}
- 输出
number
变量,然后运行此程序:
fun main() {
var number: Int? = 10
println(number)
}
输出符合预期,如下所示:
10
- 为
number
变量重新赋予null
,以确认该变量可为 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. 处理可为 null 的变量
在前面的课程中,我们已学习如何使用 .
运算符访问不可为 null 的变量的方法和属性。在本部分中,我们将了解如何运用这项技巧访问可为 null 的变量的方法和属性。
如需访问不可为 null 的 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
变量的值中有 9 个字符(包括空格)。您喜爱的演员名称中的字符数可能有所不同。
访问可为 null 的变量的属性
假设您想将 favoriteActor
变量设置为可为 null,以便没有喜爱演员的用户可以为该变量赋予 null
。
如需访问可为 null 的 favoriteActor
变量的属性,请按以下步骤操作:
- 将
favoriteActor
变量类型更改为可为 null 类型,然后运行此程序:
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 类型之前,检查该变量是否可为 null
的过程。如果您想将可为 null 的值用作不可为 null 类型,则需要明确执行 null
检查。如需了解详情,请参阅本 Codelab 后面的使用 if/else
条件部分。
在此示例中,系统不允许直接引用 favoriteActor
变量的 length
属性,因为该变量有可能是 null
,因此代码在编译时失败。
接下来,您将学习用来处理可为 null 类型的各种技巧和运算符。
使用 ?.
安全调用运算符
您可以使用 ?.
安全调用运算符访问可为 null 变量的方法或属性。
如需使用 ?.
安全调用运算符访问方法或属性,请在变量名称后面添加 ?
符号,并使用 .
表示法访问方法或属性。
?.
安全调用运算符可让您更安全地访问可为 null 的变量,因为 Kotlin 编译器会阻止变量成员为访问 null
引用而进行的任何尝试,并针对访问的成员返回 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 的变量的方法或属性。
您需要在可为 null 的变量后面添加 !!
非 null 断言运算符,之后再跟 .
运算符,最后添加不含任何空格的方法或属性。
顾名思义,如果您使用 !!
非 null 断言运算符,即表示您断言变量的值不是 null
,无论变量是否为该值都是如此。
与 ?.
安全调用运算符不同,当可为 null 的变量确实为 null
时,使用 !!
非 null 断言运算符可能会导致系统抛出 NullPointerException
错误。因此,只有在变量始终为不可为 null 或设置了适当的异常处理时,才应使用该断言运算符。如果异常未得到处理,便会导致运行时错误。您将在本课程后面的单元中了解异常处理。
如需使用 !!
非 null 断言运算符访问 favoriteActor
变量的属性,请按以下步骤操作:
- 为
favoriteActor
变量重新赋予喜爱演员的名称,然后在println()
语句中将?.
安全调用运算符替换为!!
非 null 断言运算符:
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
,否则不建议使用 !!
非 null 断言运算符。
使用 if/else
条件
您可以在 if/else
条件中使用 if
分支来执行 null
检查。
如需执行 null
检查,您可以使用 !=
比较运算符检查可为 null 的变量是否不等于 null
。
if/else
语句
if/else
语句可以与 null
检查一起使用,如下所示:
将 null 检查与 if/else
语句结合使用具有以下优点:
nullableVariable != null
表达式的null
检查会被用作if
条件。if
分支中的“body 1”会假定变量不可为 null。因此,在这个主体中,您可以随意访问变量的方法或属性,就好像变量是不可为 null 的变量一般,而不必使用?.
安全调用运算符或!!
非 null 断言运算符。else
分支中的“body 2”会假定变量为null
。因此,在这个主体中,您可以添加应在变量为null
时运行的语句。else
分支是可选的。当null
检查失败时,您只能使用if
条件来运行null
检查,而不执行默认操作。
如果有多行代码使用可为 null 的变量,那么将 null
检查与 if
条件搭配使用会更方便。相比之下,?.
安全调用运算符更适用于对可为 null 变量的单次引用。
如需编写一个 if/else
语句来对 favoriteActor
变量执行 null
检查,请按以下步骤操作:
- 再次为
favoriteActor
变量赋予喜爱演员的名称,然后移除println()
语句:
fun main() {
var favoriteActor: String? = "Sandra Oh"
}
- 添加一个具有
favoriteActor != null
条件的if
分支:
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
}
}
- 在
if
分支的主体中,添加会接受"The number of characters in your favorite actor's name is ${favoriteActor.length}"
字符串的println
语句,然后运行此程序:
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.
您喜爱的演员名称中的字符数可能有所不同。
请注意,由于您会在 null
检查之后访问 if
分支中的 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
分支的主体中,添加会接受"You didn't input a name."
字符串的println
语句:
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
表达式结合使用,以将可为 null 的变量转换为不可为 null 的变量。
如需为 if/else
表达式赋予不可为 null 类型,请按以下步骤操作:
- 将
nullableVariable != null
null
检查用作if
条件。 if
分支中的“body 1”会假定变量不可为 null。因此,在这个主体中,您可以访问变量的方法或属性,就好像变量是不可为 null 的变量一般,而不必使用?.
安全调用运算符或!!
非 null 断言运算符。else
分支中的“body 2”会假定变量为null
。因此,在这个主体中,您可以添加应在变量为null
时运行的语句。- 在主体 1 和 2 的最后一行中,您需要使用会生成不可为 null 类型的表达式或值,以便在
null
检查通过或失败时,将该表达式或值赋给不可为 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() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = if(favoriteActor != null) {
favoriteActor.length
} else {
}
}
favoriteActor
变量的 length
属性可直接使用 .
运算符进行访问。
- 在
else
分支的主体中,添加0
值:
fun main() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = if(favoriteActor != null) {
favoriteActor.length
} else {
0
}
}
当名称为 null
时,0
值会用作默认值。
- 在
main()
函数的末尾,添加一个带有"The number of characters in your favorite actor's name is $lengthOfName."
字符串的println
语句,然后运行此程序:
fun main() {
val 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
变量设置为可为 null 的favoriteActor
变量,并使用?.
安全调用运算符调用其length
属性:
fun main() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = favoriteActor?.length
println("The number of characters in your favorite actor's name is $lengthOfName.")
}
- 在
length
属性之后,添加后跟0
值的?:
Elvis 运算符,然后运行此程序:
fun main() {
val 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 的变量。 - 可将
null
赋给可为 null 的变量。 - 若要访问可为 null 的变量的方法或属性,您需要使用
?.
安全调用运算符或!!
非 null 断言运算符。 - 您可以将
if/else
语句与null
检查搭配使用,以在不可为 null 的上下文中访问可为 null 的变量。 - 您可以使用
if/else
表达式将可为 null 的变量转换为不可为 null 类型。 - 您可以使用
if/else
表达式或?:
Elvis 运算符,在可为 null 的变量为null
时,提供默认值。