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
关键字定义的变量重新赋值给相同类型不同的值。例如,你可以将一个用一个名称声明的 name
变量重新赋值给另一个名称,只要新名称为 String
类型。
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() {
}
- 创建一个
number
变量,其类型为可空Int
类型,然后将其赋值为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()
函数中的所有代码,然后声明一个favoriteActor
变量,其类型为String
类型,并将其赋值为你最喜欢的演员的姓名
fun main() {
var favoriteActor: String = "Sandra Oh"
}
- 使用
length
属性打印favoriteActor
变量值的字符数,然后运行此程序
fun main() {
var favoriteActor: String = "Sandra Oh"
println(favoriteActor.length)
}
输出符合预期
9
favoriteActor
变量的值中有 *9* 个字符,包括空格。你最喜欢的演员姓名的字符数可能不同。
访问可空变量的属性
假设你想使 favoriteActor
变量可空,以便那些没有最喜欢的演员的人可以将该变量赋值为 null
。
要访问可空 favoriteActor
变量的属性,请按照以下步骤操作
- 将
favoriteActor
变量类型更改为可空类型,然后运行此程序
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor.length)
}
你将收到此错误消息
此错误是 *编译错误*。如之前的代码实验室所述,当 Kotlin 由于代码中的语法错误而无法编译代码时,就会发生编译错误。
Kotlin 有意地应用语法规则,以便它可以实现 null
*安全性*,它指的是保证 *不会在可能为* null
*的变量上意外调用成员*。这并不意味着变量不能为 null
。这意味着如果访问了变量的成员,则该变量不能为 null
。
这至关重要,因为如果尝试访问一个为 null
的变量的成员 - 称为 null
*引用* - 在应用程序运行期间,应用程序会崩溃,因为 null
变量不包含任何属性或方法。这种类型的崩溃被称为 *运行时错误*,其中错误发生在代码编译并运行之后。
由于 Kotlin 的 null
安全特性,此类运行时错误得到了避免,因为 Kotlin 编译器会强制对可空类型进行 null
*检查*。Null
*检查* 指的是在访问变量并将其视为不可空类型之前,检查该变量是否可能为 null
的过程。如果你希望将可空值用作其不可空类型,则需要显式执行 null
检查。你将在本代码实验室后面的 **使用** 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
*检查*。
要执行 null
检查,您可以使用 !=
比较运算符检查可空变量是否不等于 null
。
if/else
语句
if/else
语句可以与 null
检查一起使用,如下所示
当与 if/else
语句结合使用时,空检查非常有用
nullableVariable != null
表达式的空检查用作if
条件。if
分支内的主体 1 假设变量不为null
。因此,在此主体中,您可以像访问非空变量一样自由地访问变量的方法或属性,而无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的主体 2 假设变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。else
分支是可选的。您只需使用if
条件来运行null
检查,而无需在null
检查失败时提供默认操作。
当有多行代码使用可空变量时,使用 if
条件进行 null
检查会更方便。相比之下,对于可空变量的单个引用,?.
安全调用运算符更方便。
要为 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
分支的主体中,添加一个接受"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
表达式结合使用,以将可空变量转换为非空变量。
要将 if/else
表达式分配给非空类型
nullableVariable != null
null
检查用作if
条件。if
分支内的主体 1 假设变量不为null
。因此,在此主体中,您可以像访问非空变量一样访问变量的方法或属性,而无需使用?.
安全调用运算符或!!
非空断言运算符。else
分支内的主体 2 假设变量为null
。因此,在此主体中,您可以添加在变量为null
时应运行的语句。- 在主体 1 和 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
}
}
当名称为 null
时,0
值用作默认值。
- 在
main()
函数的末尾,添加一个接受"The number of characters in your favorite actor's name is $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
。 - 要访问可空变量的方法或属性,您需要使用
?.
安全调用运算符或!!
非空断言运算符。 - 您可以使用
if/else
语句和null
检查来在不可空上下文中访问可空变量。 - 您可以使用
if/else
表达式将可空变量转换为不可空类型。 - 当可空变量为
null
时,您可以使用if/else
表达式或?:
Elvis运算符提供一个默认值。