1. 程式人生 > 其它 >第六章 null 安全與異常

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

這 5 個先決條件函式中,require函式尤其有用。其他函式可以利用它指定自身值參的邊界

fun juggleSwords(swordsJuggling: Int) {
    require(swordsJuggling >= 3) {"Juggle at least 3 swords to be exciting."}
}
歡迎轉載,轉載請註明出處!