在 Kotlin 中使用空值性

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"

A box that represents a favoriteActor variable that's assigned a

但是,如果您没有最喜欢的演员怎么办?您可能希望为变量赋值"Nobody""None"值。这不是一个好方法,因为您的程序会将favoriteActor变量解释为具有"Nobody""None"值,而不是根本没有值。在 Kotlin 中,您可以使用null来表示变量没有关联的值。

A box that represents afavoriteActor variable that's assigned a null value.

要在代码中使用null,请按照以下步骤操作

  1. Kotlin Playground中,将main()函数主体中的内容替换为设置为nullfavoriteActor变量
fun main() {
    val favoriteActor = null
}
  1. 使用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,请按照以下步骤操作

  1. val关键字更改为var关键字,然后指定favoriteActor变量为String类型并将其赋值为您最喜欢的演员的姓名
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor)
}
  1. 删除println()函数
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. favoriteActor变量重新赋值为null,然后运行此程序
fun main() {
    var favoriteActor: String = "Sandra Oh"
    favoriteActor = null
}

您将收到此错误消息

A warning message that says:

在 Kotlin 中,可空类型和非空类型之间存在区别

  • 可空类型是可以保存null的变量。
  • 非空类型是不能保存null的变量。

只有在您明确允许它保存null时,类型才可为空。正如错误消息所说,String数据类型是非空类型,因此您不能将变量重新赋值为null

A diagram that shows how to declare nullable type variables. It starts with a var keyword followed by the name of the variable block, a semi colon, the type of the variable, a question mark, the equal sign, and the value block.  The type block and the question mark is denoted with Nullable type text marking that having the type followed with question mark is what makes it a nullable type.

要在 Kotlin 中声明可空变量,需要在类型的末尾添加?运算符。例如,String?类型可以保存字符串或null,而String类型只能保存字符串。要声明可空变量,需要显式添加可空类型。如果没有可空类型,Kotlin 编译器会推断它是非空类型。

  1. favoriteActor变量类型从String数据类型更改为String?数据类型
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    favoriteActor = null
}
  1. 打印null重新赋值之前和之后favoriteActor变量的值,然后运行此程序
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor)

    favoriteActor = null
    println(favoriteActor)
}

输出如下代码片段所示

Sandra Oh
null

favoriteActor变量最初保存一个字符串,然后重新赋值为null

试试看

现在您可以使用可空String?类型了,您可以使用Int值初始化变量并将其重新赋值为null吗?

编写一个可空Int

  1. 删除main()函数中的所有代码
fun main() {
    
}
  1. 创建一个可空Int类型的number变量,然后将其赋值为10
fun main() {
    var number: Int? = 10
}
  1. 打印number变量,然后运行此程序
fun main() {
    var number: Int? = 10
    println(number)
}

输出符合预期

10
  1. number变量重新赋值为null,以确认该变量可为空
fun main() {
    var number: Int? = 10
    println(number)
    
    number = null
}
  1. 添加另一个println(number)语句作为程序的最后一行,然后运行它
fun main() {
    var number: Int? = 10
    println(number)
    
    number = null
    println(number)
}

输出符合预期

10
null

3. 处理可空变量

前面,您学习了如何使用.运算符访问非空变量的方法和属性。在本节中,您将学习如何使用它来访问可空变量的方法和属性。

要访问非空favoriteActor变量的属性,请按照以下步骤操作

  1. 删除main()函数中的所有代码,然后声明String类型的favoriteActor变量并将其赋值为您最喜欢的演员的姓名
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. 使用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)
}

您将收到此错误消息

An error message that says,

这是一个 *编译错误*。如之前的代码实验室所述,当 Kotlin 由于代码中的语法错误而无法编译代码时,就会发生编译错误。

Kotlin 有意应用语法规则,以便可以实现null *安全性*,这意味着 *保证不会对可能为*null*的变量进行意外调用*。这并不意味着变量不能为null。这意味着如果访问变量的成员,则变量不能为null

这一点至关重要,因为如果尝试访问运行应用程序期间为null的变量的成员(称为null *引用*),则应用程序会崩溃,因为null变量不包含任何属性或方法。这种类型的崩溃称为 *运行时错误*,其中错误发生在代码编译并运行之后。

由于Kotlin的空安全特性,这类运行时错误可以得到避免,因为Kotlin编译器会强制对可空类型进行空值检查。Null检查是指在访问变量并将其视为非空类型之前,检查变量是否可能为null的过程。如果要将可空值用作其非空类型,则需要显式执行空值检查。您将在本Codelab后面的**使用if/else条件语句**部分了解到这一点。

在这个例子中,代码在编译时失败,因为不允许直接引用favoriteActor变量的length属性,因为该变量有可能为null

接下来,您将学习处理可空类型的各种技术和运算符。

使用?.安全调用运算符

您可以使用?.安全调用运算符来访问可空变量的方法或属性。

A diagram that shows a nullable variable block followed by a question mark, a dot, and a method or property block. There are no spaces in between.

要使用?.安全调用运算符访问方法或属性,请在变量名后添加一个?符号,并使用.符号访问方法或属性。

?.安全调用运算符允许更安全地访问可空变量,因为Kotlin编译器会阻止任何对null引用的成员访问尝试,并为被访问的成员返回null

要安全地访问可空favoriteActor变量的属性,请按照以下步骤操作

  1. println()语句中,将.运算符替换为?.安全调用运算符
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor?.length)
}
  1. 运行此程序,然后验证输出是否符合预期
9

您最喜欢的演员名字的字符数可能不同。

  1. favoriteActor变量重新赋值为null,然后运行此程序
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor?.length)
}

您将看到此输出

null

请注意,尽管尝试访问null变量的length属性,但程序不会崩溃。安全调用表达式只会返回null

使用!!非空断言运算符

您也可以使用!!非空断言运算符来访问可空变量的方法或属性。

A diagram that shows a nullable variable block followed by two exclamation points, a single dot, and a method or property block. There are no spaces in between.

在可空变量之后,您需要添加!!非空断言运算符,后跟.运算符,然后是方法或属性,中间没有任何空格。

顾名思义,如果您使用!!非空断言,则表示您断言变量的值不为null,无论它是否为null

?.安全调用运算符不同,如果可空变量确实是null,则使用!!非空断言运算符可能会导致抛出NullPointerException错误。因此,只有在变量始终是非空变量或已设置适当的异常处理时才应这样做。如果未处理,异常会导致运行时错误。您将在本课程的后续单元中学习异常处理。

要使用!!非空断言运算符访问favoriteActor变量的属性,请按照以下步骤操作

  1. favoriteActor变量重新赋值为您最喜欢的演员姓名,然后在println()语句中将?.安全调用运算符替换为!!非空断言运算符
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor!!.length)
}
  1. 运行此程序,然后验证输出是否符合预期
9

您最喜欢的演员名字的字符数可能不同。

  1. favoriteActor变量重新赋值为null,然后运行此程序
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor!!.length)
}

您将得到一个NullPointerException错误

An error message that says,

此Kotlin错误表明您的程序在执行期间崩溃。因此,除非您确定变量不为null,否则不建议使用!!非空断言运算符。

使用if/else条件语句

您可以使用if/else条件语句中的if分支执行空值检查。

A diagram that shows a nullable variable block followed by an exclamation point, an equal sign, and null.

要执行空值检查,您可以使用!=比较运算符检查可空变量是否不等于null

if/else语句

if/else语句可以与空值检查一起使用,如下所示

A diagram that describes an if/else statement with the if keyword followed by parentheses with a null check block inside them, a pair of curly braces with body 1 inside them, an else keyword, and another pair of curly braces with a body 2 block inside them. The else clause is encapsulated with a dotted red box, which is annotated as optional.

空值检查与if/else语句结合使用非常有用

  • nullableVariable != null表达式的空值检查用作if条件。
  • if分支内的主体1假定变量不为null。因此,在此主体中,您可以像使用非空变量一样自由地访问变量的方法或属性,无需使用?.安全调用运算符或!!非空断言运算符。
  • else分支内的主体2假定变量为null。因此,在此主体中,您可以添加在变量为null时应运行的语句。else分支是可选的。您可以仅使用if条件来运行空值检查,而无需在空值检查失败时提供默认操作。

当有多行代码使用可空变量时,空值检查与if条件结合使用更方便。相比之下,?.安全调用运算符对于可空变量的单个引用更方便。

要为favoriteActor变量编写带有空值检查的if/else语句,请按照以下步骤操作

  1. 再次将favoriteActor变量赋值为您最喜欢的演员姓名,然后删除println()语句
fun main() {
    var favoriteActor: String? = "Sandra Oh"

}
  1. 添加一个带有favoriteActor != null条件的if分支
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {

    }
}
  1. 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,因此编译器允许直接访问该属性。

  1. 可选:添加一个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 {

    }
}
  1. 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.")
    }
}
  1. 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表达式结合使用,以将可空变量转换为非空变量。

A diagram that describes an if/else expression with the val keyword followed by a name block, a colon, and non-null type block, an equal symbol, the if keyword, parentheses with a condition inside them, a pair of curly braces with body 1 inside them, an else keyword with another pair of curly braces, and a body 2 block inside them.

if/else表达式赋值给非空类型

  • nullableVariable != null空值检查用作if条件。
  • if分支内的主体1假定变量不为null。因此,在此主体中,您可以像使用非空变量一样访问变量的方法或属性,无需使用?.安全调用运算符或!!非空断言运算符。
  • else分支内的主体2假定变量为null。因此,在此主体中,您可以添加在变量为null时应运行的语句。
  • 在主体1和2的最后一行,您需要使用一个表达式或值,该表达式或值的结果为非空类型,以便在空值检查通过或失败时分别将其赋值给非空变量。

要使用if/else表达式重写程序,使其只使用一个println语句,请按照以下步骤操作

  1. 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.")
    }
}
  1. 创建一个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.")
    }
}
  1. 删除ifelse分支中的两个println()语句
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      
    } else {
      
    }
}
  1. if分支的主体中,添加一个favoriteActor.length表达式
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      favoriteActor.length
    } else {
      
    }
}

使用.运算符直接访问favoriteActor变量的length属性。

  1. else分支的主体中,添加一个0
fun main() {
   var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }
}

0值用作名称为null时的默认值。

  1. 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 运算符 `?:` 之后的表达式。

A diagram that shows the val keyword followed by a name block, an equal sign, a nullable variable block, a question mark, a dot, a method or property block, a question mark, a colon, and a default value block.

要修改之前的程序以使用 Elvis 运算符 `?:`,请按照以下步骤操作:

  1. 移除 `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.")
}
  1. 在 `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` 时提供默认值。

了解更多