iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
0
Mobile Development

30天,從0開始用Kotlin寫APP系列 第 6

Day 06 | Kotlin 中的 Null Safety 與 Scope Function

Null Safety

Null Safety 應該是每個介紹 Kotlin 的文章或影片都會提到的 Part ,也是因為這個改動,讓他和 Java 開始有分歧,因為解決了 Java 中長期困擾開發者的 NullPointerException( NPE )問題,即使開發時已經小心再小心,但還是會有防不勝防的時候,而現在 Kotlin compiler 能幫你做完 Null check

但以下的狀況還是有可能引發 NullPointerException

  • 調用 throw NullPointerException()
  • 使用 !! operator,等等會解釋這個 Operator 的意思已經可能會造成的問題
  • 初始化的某些數據不一致,例如:
    • 使用或傳遞 Constructor 中沒有初始化的 this
    • Superclass constructor 調用實現未初始化衍生類別的 Open 成員
  • 調用外部的 Java code 本身存在 NPE 的問題

Kotlin 中的 null 相當於空指標,那該如何告訴 Compiler 這個變數能不能接受 null 呢? 只要透過 變量 變數名稱: 型態? = null 即可

var a: String? = "Nullable"
var b: String = "NonNullable"
println(a) // Output: Nullable
println(b) // Output: NonNullable
a = null
b = null // 會發生 Error, Null can not be a value of a non-null type String
println(a) // Output: null

但如果更進一步思考,如果今天變數是可接受 Null ,那去調用變數的成員函數時, Compiler 要怎麼知道現在變數的值存不存在呢? 因此就要提到 Safe Calls

Safe Calls(?) 與 Not-null Assertion( !! )

在調用 Nullable 變數內的成員函數時, Compiler 為了防止 NPE 的發生,會要求使用 變數?.成員函式 來防止 NPE,當 Compiler 看到時會先檢查這個變數內部是不是有值,如果是 null 便不會執行成員函數,那也就不會引發 NPE 囉~

Example:

val a: String? = "Nullable"
println(a.length) 
// Output: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
println(a?.length)
// Output: 8

而 Safe Calls 是可以一直串聯下去的,當其中有一個結果是 null ,則 Compiler 就會回傳 null。 For Example, 假設有一個人叫 Alice ,他可能會住在某個地方的某座大樓裏面的某一層樓(但也許他不是住在大樓,而是住透天,因此在大樓的部份就會拿到 null ),那麼用 Safe Calls 就可以這樣表示

Example:

val floor: Int? = alice.location?.building?.floor

// 如果全部都不是 null ,則 floor 裡面就會是 Alice 住的樓層
// 但如果其中一個是 null , 則 floor 裡面就會是 null

但這樣雖然在呼叫 Alice 的成員函數時不會發生 NPE 的問題,但 floor 裡面還是可能會存在 null,因此就會搭配 貓王運算子( Elvis Operator ) 做使用,具體使用方法可以看這篇喔~

另外,如果是用 Android Studio 內建的 Convert java to kotlin 功能,那麼會發現轉換後的 Kotlin code 會存在大量的 !! ,那這個符號是告訴 Compiler 說這個值會不會 null 交給開發者來管理,這其實就是和過去開發 Java 的狀況一樣,開發者要自己去注意變數內容是不是 null,因此如果加上 !! 就有機會引發 NPE 的狀況,因此在寫 Kotlin 時,還是盡可能用 Safe calls 才能享受到 Kotlin 的精髓

Safe Casts

做轉型時,當 Compiler 發現這個變數的型態不能轉成另外一個型態時,在 Java 中會丟出 cannot be cast to 的 Error,但在 Kotlin 中可以採用Safe Casts ,目的也是與上面提到的 Safe Calls 相同,預防 Error 的發生,並且還可以搭配 Elvis Operator ,當變數轉型失敗時,塞一個預設值給變數

// 如果沒採用 Safe Casts, 採用直接轉型
val aString: String = "123"
val aInt: Int = aString as Int
print(aInt) // Output: 錯誤,java.lang.String cannot be cast to java.lang.Integer

// 如果採用 Safe Casts, 但沒搭配 Elvis Operator
val aString: String = "123"
val aInt: Int = aString as? Int
print(aInt) // Output: null,如果去掉用 aInt 的成員函數,可能會引發 NPE

// 如果採用 Safe Casts, 並搭配 Elvis Operator
val aString: String = "123"
val aInt: Int = aString as? Int ?: 0
print(aInt) // Output: 0

Scope Function

在寫 Kotlin 的時候會發現很常使用到 letrunalsoapply 關鍵詞,這些就是 Scope function ,這些關鍵詞的目的是能讓 Object 中的 context 依照這些關鍵詞包起來的 code block 順序執行, 想深入了解 Scope function 可以參考這幾篇文章

那 Scope function 分那麼多種關鍵詞的目的為何呢? 根據 Kotlin 的 scope function: apply, let, run..等等的解釋:

這些 functions 目的是希望,執行程式碼裡面的 function literal 的時候有更好的可讀性。
[color=orange]

在我看來大多數狀況也符合這篇所言,但還是會在某些狀況下產生差別,例如這篇的例子

採用 run :

val result ="Hello".run {
    println(this+" World")

    this + " World" // run 返回最後一行的值
}
println(result)
// Output:
// Hello World
// Hello World

採用 apply :

val result ="Hello".apply {
    println(this+" World")

    this + " World" // apply 返回自己,所以 result 還是 Hello
}
println(result)
// Output:
// Hello World
// Hello

因此在使用上還是必須思考後在決定該要使用何種 Scope function 才達到目的

結論

今天介紹了 Kotlin 中非常重要的核心精髓 Null Safety,另外還有 Kotlin 中很常見的 Scope function,明天會再提到 Kotlin 中的 Extensions 喔

Reference


上一篇
Day 05 | Kotlin 中的條件式、循環式與跳轉方法
下一篇
Day 07 | Kotlin 中的擴展( Extensions )與高階函數( Higher-Order Function )- Part 1
系列文
30天,從0開始用Kotlin寫APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言