1. 开始之前
此代码实验室将向您讲解空值性以及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
?
在第一单元中,您学习了声明变量时需要立即为其赋值。例如,声明favoriteActor
变量时,您可以立即为其赋值"Sandra Oh"
字符串值。
val favoriteActor = "Sandra Oh"
但是,如果您没有最喜欢的演员怎么办?您可能希望为变量赋值"Nobody"
或"None"
值。这不是一个好方法,因为您的程序会将favoriteActor
变量解释为具有"Nobody"
或"None"
值,而不是根本没有值。在 Kotlin 中,您可以使用null
来表示变量没有关联的值。
要在代码中使用null
,请按照以下步骤操作
- 在Kotlin Playground中,将
main()
函数主体中的内容替换为设置为null
的favoriteActor
变量
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)
}
您将收到此错误消息
这是一个 *编译错误*。如之前的代码实验室所述,当 Kotlin 由于代码中的语法错误而无法编译代码时,就会发生编译错误。
Kotlin 有意应用语法规则,以便可以实现null
*安全性*,这意味着 *保证不会对可能为*null
*的变量进行意外调用*。这并不意味着变量不能为null
。这意味着如果访问变量的成员,则变量不能为null
。
这一点至关重要,因为如果尝试访问运行应用程序期间为null
的变量的成员(称为null
*引用*),则应用程序会崩溃,因为null
变量不包含任何属性或方法。这种类型的崩溃称为 *运行时错误*,其中错误发生在代码编译并运行之后。
由于Kotlin的空安全特性,这类运行时错误可以得到避免,因为Kotlin编译器会强制对可空类型进行空值检查。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
错误。因此,只有在变量始终是非空变量或已设置适当的异常处理时才应这样做。如果未处理,异常会导致运行时错误。您将在本课程的后续单元中学习异常处理。
要使用!!
非空断言运算符访问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
。
if/else
语句
if/else
语句可以与空值检查一起使用,如下所示
空值检查与if/else
语句结合使用非常有用
nullableVariable != null
表达式的空值检查用作if
条件。if
分支内的主体1假定变量不为null
。因此,在此主体中,您可以像使用非空变量一样自由地访问变量的方法或属性,无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的主体2假定变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。else
分支是可选的。您可以仅使用if
条件来运行空值检查,而无需在空值检查失败时提供默认操作。
当有多行代码使用可空变量时,空值检查与if
条件结合使用更方便。相比之下,?.
安全调用运算符对于可空变量的单个引用更方便。
要为favoriteActor
变量编写带有空值检查的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
分支的主体中,添加一个接受"您最喜欢的演员名字的字符数是 ${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.
您最喜欢的演员名字的字符数可能不同。
请注意,您可以直接使用.
运算符访问名称的length方法,因为您在空值检查后在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
分支的主体中,添加一个接受"您没有输入姓名。"
字符串的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
表达式
您还可以将空值检查与if/else
表达式结合使用,以将可空变量转换为非空变量。
将if/else
表达式赋值给非空类型
nullableVariable != null
空值检查用作if
条件。if
分支内的主体1假定变量不为null
。因此,在此主体中,您可以像使用非空变量一样访问变量的方法或属性,无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的主体2假定变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。- 在主体1和2的最后一行,您需要使用一个表达式或值,该表达式或值的结果为非空类型,以便在空值检查通过或失败时分别将其赋值给非空变量。
要使用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()
函数的末尾,添加一个接受"您最喜欢的演员名字的字符数是 $lengthOfName。"
字符串的println
语句,然后运行此程序
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
` 时提供默认值。