Kotlin 線上讀書會 筆記 (三) Null Safety and Exception
這篇的範圍是第六章 Null Safety And Exception
Null Safety
在看Null Safety之前,我們先來看一下為什麼要有Null。
例如我們想要記錄一個學生的考試成績,而這個欄位自然會使用Int的型別來記錄考試的分數,但如果是缺考的話,我們要怎麼在這個Int的型別來記錄缺考。
//記錄考試成績
var score:Int
如果用0來記錄缺考是不適合的,因為考0分跟缺考是不一樣的,這時候我們要用null來表達缺考是較適合的。
var score:Int?
score = 0 //指的是0分,不能表達出缺考
score = null // 缺考
再舉另一個例子,寫App經常會需要呼叫API取得Json資料,例如你在做一個訂房的App,跟Web API取得飯店的資料。我們通常會把Json的結構對映到程式碼裡的資料模型。
{
"HotelName": "xx飯店",
"HotelEnglishName": "xx Hotel",
"HotelTraffic": {
"Hotel_Address": "台北市XX路…",
"Hotel_Lat": 24.222,
"Hotel_Lng": 125.222
}
}
可能API團隊就會跟你說Hotel_Traffic在某些情境是沒有的,這時候必須在HotelTraffic給null,因為如果用空白或是座標給奇怪的數字來表達沒有,都不太適合。
{
"HotelName": "xx飯店",
"HotelEnglishName": "xx Hotel",
"HotelTraffic": null
}
所以從現實生活中或從需求上來看,都是有「沒有」這件事的存在,也就有使用null的需求。
NullPointerException
null 的出現,但卻也造成了經常NullPointerException的錯誤。
String string = null;
string.length();
在Java 執行上面這段程式碼,是會出現NullPointerException的,因為string是null,卻呼叫length方法。更糟糕的是這個錯誤在執行階段才會知道,編譯器並不會事先告訴你有錯誤。
Null Safety
Kotlin在處理null可能發生的問題的作法就是,在宣告變數時先講清楚,到底可不可以是null。如果可以有null,編譯器就會提早檢查你是否有針對null做處理。
下例的程式碼,在宣告的類別後面加上?用來表示這個變數是可以null的。
//可以null
var name: String?//不能null
var name2: String
宣告了一個可為null的變數後,如果直接呼叫name.length,編譯器就會警告錯誤。
var name: String? = null
println(name.length)
我們可按下Option + Enter ,IntelliJ會給予這些建議來修正。
選擇第1個Surround with null check,程式碼會加上if判斷是否null,這樣就確保了當name是null的時候,不會呼叫length方法。
var name: String? = null
if (name != null) {
println(name.length)
}
選擇第2個Replace with salfe(?.) call,則表示當name不是null的時候才呼叫length。
var name: String? = null
println(name?.length)
第3個方式,add non-null asserted(!!) call。 加上!!表示name一定不會是null,編譯器就不會再檢查。這種用法在使用上要特別小心,因為你必須真的確認不會是null,否則一執行還是會發生NullPointerException。
var name: String? = null
println(name!!.length)
function的參數也是可以null的
function 裡的參數加上?後,表示該參數可以為null。當這樣寫時,你的function就必須處理可能有null的情境。所以如果沒有必要,就不要加上?,因為這樣就能確保function的參數傳進來的不可能是null。
fun setName(str: String?) {
//需處理傳進來str可能有null
}fun setName(str: String) {
//不可能會有null進來了
}
如果傳的參數有null,但方法參數不允許,編譯器就會跳出錯誤。
fun main() {
var name: String?
setName(name)
}fun setName(str: String) {
//不可能會有null進來了
}
按下Option + Enter,IntelliJ一樣會給建議的做法
第2種Wrap with ‘?.let{…}’ call。會在呼叫方法前加上.let,在name不為null時,才會執行大括號裡面的程式碼。
name?.let { setName(it) }
回傳型別
回傳型別也可以宣告為可null。
fun main() {
var str = getName()
}fun getName() : String?{
return "A"
}
例如這個例子,getLocation的功能是傳入地址回傳座標Location?,回傳的Location是可以null的,這表示著傳入的座標如果找不到的話,就會回傳null。
fun main() {
getLocation("地址")
}//找座標,用Location?來表示找不到
fun getLocation(address: String) : Location?{
if (找不到座標){
return null
}
return Location(1.0,1.0)
}class Location (val lat:Double, val lng:Double)
Elvis operator
你可以用 ?: 來表示當變數為null時,該回傳的值
name?.length ?: 0
Primitive type
在Kotlin沒有Primitive type,所以Int、Boolean還是可以有null。
var a: Int? var: b:Boolean?
Exception
Exception 用來表達程式出了問題,你可以在程式碼有問題的地方加上throw Exception()
throw Exception("Hi There!")
什麼情況要丟出Exception呢?如下列程式碼,我要寫一個除法,傳入a、b兩個參數,而b不能為0。我們就可以在function裡先加上檢查,如果b=0就拋出一個異常。
fun division(a:Int, b:Int) :Int{
if (b == 0) {
throw Exception("b不可為0")
}
return a/b
}
catch exception
當錯誤發生時,可以使用try、catch把錯誤補抓下來。在catch裡面做當錯誤發生時應該做的處理。而finally指的是不管有沒有錯誤都要執行的區域。
try {
// some code
}
catch (e: SomeException) {
// handler
}
finally {
// optional finally block
}
不要忽略錯誤
不要在catch裡沒有任何的處理,直接忽略錯誤。
try {
log.append(message)
}
catch (IOException e) {
// Must be safe
}
Exception 之後
發生錯誤之後,記得在你的UI層級要去補抓錯誤,並告訴使用者現在應該怎麼辦。常見的做法是Alert一個錯誤的訊息。
自定義異常
你可以透過繼承RuntimeException來自定義一個Exception類別。自定義異常類別,可以更清楚拋出的異常是屬於哪一類的異常。下列就把呼叫WebAPI會發生的異常補抓下來,判斷Exception屬於APIFail還是APIForbidden,就可以做對映的處理。
fun main() {
try {
callAPI()
}catch (e:Exception){
when (e) {
is APIFail -> {
println("API Fail的處理")
}
is APIForbidden -> {
println("API Forbidden 的處理")
}
}
}
}
fun callAPI(): String {
throw APIFail()
}class APIFail():RuntimeException()class APIForbidden():RuntimeException(
你也可以在自訂義的Exception類別裡,加上變數用來存放更多資訊。
fun getLevel(number: Int, type: String): String {
throw CustomError(1, "錯誤訊息")
}class CustomError(var errorCode:Int, var errorMessage:String):RuntimeException(){}
Precondiction Exception
checkNotNull
如果參數是null,拋出IllegalStateException異常,否則回傳參數值
require
如果參數是false,拋出IllegalArgumentException異常
requireNotNull
如果參數是null,拋出IllegalStateException異常,否則回傳參數值
error
拋出異常訊息
assert
如果參數是false,拋出AssertionError異常
Elvis operator
你還可以使用?:的方式,在變數為null時拋出異常。
val s = name ?: throw IllegalArgumentException("Name required")
👊 給Android 初學者 的快速成長 線上課程
1️⃣ UI 進階實戰 — Material Design Component 讓你簡單做出效果超好的UI
2️⃣ 動畫入門到進階 — 用動畫提升使用者體驗
3️⃣ 架構設計 — MVP、MVVM 讓你程式碼好維護
🆙 3堂組合包更划算 — Android 架構設計 + 動畫入門到進階 + UI 進階實戰