第六章 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."}
}
歡迎轉載,轉載請註明出處!