第六章 null 安全与异常
6.1 可空性
- 可为空: 可以赋
null
值 - 不可为空: 不能被赋
null
值
fun main(args: Array<String>) {
var signatureDrink = "Buttered Ale"
signatureDrink = null
}
会报错:Null can not be a value of a non-null type String
因为 String 变量属于非空类型
而上述代码在 Java 中可行
String signatureDrink = "Buttered Ale";
signatureDrink = null;
但是拼接一个字符串和 signatureDrink
空值变量会抛出 NullPointerException
的异常,因为一个不存在的东西无法拼接
signatureDrink = signatureDrink + ", large";
null 值表示变量不存在,而空字符串表示变量存在且值为 ""
。所以空字符串可以拼接,而 null 不可以,会报错
在允许任何类型为 null 的语言中,NullPointerException
是应用程序崩溃的最常见原因
对于 null 值问题,除非另有规定,Kotlin 中变量不可为 null 值,这样运行时崩溃从根源上得到了解决
6.2 Kotlin 的 null 类型
readLine
函数从控制台获取用户输入,然后返回给应用程序
public fun readLine(): String?
String?
返回类型中的问号表示可空。这表示 readLine 要么返回一个 String 类型的值,要么返回 null
6.3 编译时间与运行时间
Kotlin 是一门编译型语言:Kotlin 应用代码先编译成机器语言指令,再由一个叫编译器的特殊程序执行
在编译阶段,编译器会检查代码是否符合特定要求,确认没问题后再编译生成机器指令
- 编译时错误: 在编译时捕获的错误
- 运行时错误: 在运行时出现的错误
通常来讲,编译时错误要好过运行时错误。写代码时就能发现问题显然要比运行时发现好
6.4 null 安全
操作一个可空变量,而它又可能不存在,为了应对这种风险,Kotlin 不允许在可空类型值上调用函数,除非主动接手安全管理
fun main(args: Array<String>) {
var beverage = readLine().capitalize()
println(beverage)
}
上面的代码会出现编译时错误:
Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
不让调用 captalize
函数是因为没有考虑 beverage
变量为空的情况
6.4.1 选项一:安全调用操作符
在对一个可能为 null 的变量函数调用时,使用安全操作符 ?.
,这样如果遇到 null 值,就会跳过函数调用
fun main(args: Array<String>) {
var beverage = readLine()?.capitalize()
println(beverage)
}
使用带 let 的安全调用
fun main(args: Array<String>) {
var beverage = readLine()?.let {
if (it.isNotBlank()) {
it.capitalize()
} else {
"Buttered Ale"
}
}
println(beverage)
}
6.4.2 选项二:使用 !!. 操作符
!!.
操作符,如果为 null 会抛出 KotlinNullPointerException
相比于安全调用操作符,!!. 操作符太激进,一般应避免使用
!!. 的官方名字是非空断言操作符,大师开发人员更喜欢叫“感叹号操作符”
fun main(args: Array<String>) {
var beverage = readLine()!!.capitalize()
println(beverage)
}
6.4.3 选项三:使用 if 判断 null 值情况
fun main(args: Array<String>) {
var beverage = readLine()
if (beverage != null) {
beverage = beverage.capitalize()
} else {
println("I can't do that without crashing - beverage was null!")
}
println(beverage)
}
使用空合并操作符
?:
如果左边的求值结果是 null 就使用右边的结果值
fun main(args: Array<String>) {
var beverage = readLine()
if (beverage != null) {
beverage = beverage.capitalize()
} else {
println("I can't do that without crashing - beverage was null!")
}
val beverageServed: String = beverage ?: "Buttered Ale"
println(beverageServed)
}
合并空操作符也可以和 let 函数一起使用来代替 if/else 语句
var beverage = readLine()
beverage?.let {
beverage = it.captialize()
} ?: println("I can't do that without crashing - beverage was null!")
6.5 异常
6.5.1 抛出异常
Kotlin 允许你主动示意有异常发生。这种行为又叫抛出一个异常,由throw操作符触发
在众多异常里面,IllegalStateException
是最常见的一个
import java.lang.IllegalStateException
fun main(args: Array<String>) {
var swordsJuggling: Int? = null
val isJugglingProficient = (1..3).shuffled().last() == 3
if (isJugglingProficient) {
swordsJuggling = 2
}
proficiencyCheck(swordsJuggling)
println("You juggle $swordsJuggling swords!")
}
fun proficiencyCheck(swordsJuggling: Int?) {
swordsJuggling ?: throw IllegalStateException("Player cannot juggle swords")
}
6.5.2 自定义异常
刚刚抛出的 IllegalStateException
表明程序出了不合法的状况
为了提供更多细节,可以针对某类很特殊的问题创建自定异常
class UnskilledSwordJugglerException() :
IllegalStateException("Player cannot juggle swords")
6.5.3 处理异常
通过定义包裹有问题的代码的 try/catch
语句,Kotlin 可以让你决定如何来处理异常
fun main(args: Array<String>) {
var swordsJuggling: Int? = null
val isJugglingProficient = (1..3).shuffled().last() == 3
if (isJugglingProficient) {
swordsJuggling = 2
}
try {
proficiencyCheck(swordsJuggling)
swordsJuggling = swordsJuggling!!.plus(1)
} catch (e: Exception) {
println(e)
}
println("You juggle $swordsJuggling swords!")
}
fun proficiencyCheck(swordsJuggling: Int?) {
swordsJuggling ?: throw UnskilledSwordJugglerException()
}
class UnskilledSwordJugglerException() :
IllegalStateException("Player cannot juggle swords")
6.6 先决条件函数
先决条件函数: 条件必须满足目标代码才能执行的函数
前面已经介绍了好几种避免 KotlinNullPointerException
等异常的方法,使用先决条件函数也是一种方法
checkNotNull
是一个先决条件函数,用来检查某个值是否为 null, 如果不是,就返回该值,反之就抛出 IllegalStateException
异常
fun proficiencyCheck(swordsJuggling: Int?) {
checkNotNull(swordsJuggling) { "Player cannot juggle swords" }
}
这 5 个先决条件函数中,require
函数尤其有用。其他函数可以利用它指定自身值参的边界
fun juggleSwords(swordsJuggling: Int) {
require(swordsJuggling >= 3) {"Juggle at least 3 swords to be exciting."}
}