第六章 null 安全与异常

第六章 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" }
}

第六章 null 安全与异常

这 5 个先决条件函数中,require函数尤其有用。其他函数可以利用它指定自身值参的边界

fun juggleSwords(swordsJuggling: Int) {
    require(swordsJuggling >= 3) {"Juggle at least 3 swords to be exciting."}
}
上一篇:《Android》Chap.6 广播机制BroadcastReceiver


下一篇:javaScript 函数