翻譯說明:
原標題: How to remove all !! from your Kotlin code
原文地址: android.jlelse.eu/how-to-remo…
原文作者: David Vávra
空安全特性是Kotlin語言最好語法特性之一。它讓你在語言層面來考慮可空性,以致於你可以避免很多在Java中常見的隱藏空指標異常。然而當你通過工具自動將Java程式碼轉化成Kotlin時,你會發現有很多的 !!(非空斷言) 標記出現。按道理在你的程式碼中不應該有任何的 !!(非空斷言) 出現,除非它是一個快速原型。並且我相信這是對的,因為 !!(非空斷言) 的出現基本上的意味著 “你這裡有可能存在未處理的KotlinNullPointerException”.
Kotlin有一些智慧的機制去避免這些空指標的問題,但是弄明白它並不是那麼直接和容易。這裡有6種方法去做到這一點:
1) 使用val替代var
Kotlin讓你在語言的層面去考慮不變性,這點看起來很不錯。 val 是隻讀,var 是可變。建議你儘可能多的使用只讀屬性。因為他們是執行緒安全的並且在函數語言程式設計方面效果很好。如果你使用它們時當做是不可變的,那麼你就不必關心可空性了,但是隻要注意val實際上是可變的。
2) 使用lateinit
有時候你不能使用不變屬性。例如,在Android中onCreate方法被呼叫時,一些屬性被初始化。在這些場景中,Kotlin有個語言特性叫做 lateinit
使用!!的程式碼:
private var mAdapter: RecyclerAdapter<Transaction>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mAdapter = RecyclerAdapter(R.layout.item_transaction)
}
fun updateTransactions() {
mAdapter!!.notifyDataSetChanged()
}
複製程式碼
用下面程式碼替代上面程式碼:
private lateinit var mAdapter: RecyclerAdapter<Transaction>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mAdapter = RecyclerAdapter(R.layout.item_transaction)
}
fun updateTransactions() {
mAdapter.notifyDataSetChanged()
}
複製程式碼
需要注意的是,訪問未初始化的 lateinit 修飾的屬性會丟擲UninitializedPropertyAccessException異常。
很遺憾的是lateinit 不支援基本資料型別,例如Int. 針對基本資料型別實現方式你可以使用委託(delegate)類似以下實現:
private var mNumber: Int by Delegates.notNull<Int>()
複製程式碼
3)使用let函式
這裡有個Kotlin中很常見的編譯時錯誤:
令我惱火的是:我知道這個可變屬性在空型別檢查後不能被改變。很多開發人員通過以下方式快速修復它:
private var mPhotoUrl: String? = null
fun uploadClicked() {
if (mPhotoUrl != null) {
uploadPhoto(mPhotoUrl!!)
}
}
複製程式碼
但是這裡有個優雅解決辦法,那就是使用 let函式
private var mPhotoUrl: String? = null
fun uploadClicked() {
mPhotoUrl?.let { uploadPhoto(it) }
}
複製程式碼
4)建立全域性的函式去處理更多複雜的case
let 函式是一個很好的簡單檢查空型別替代方式,但是可能會出現更多複雜的cases,例如:
if (mUserName != null && mPhotoUrl != null) {
uploadPhoto(mUserName!!, mPhotoUrl!!)
}
複製程式碼
你可以使用兩個let函式巢狀呼叫,但是那樣可讀性很差。在Kotlin中你可以全域性訪問函式,因此你可以輕鬆地建立你所需要的函式。類似如下方法:
ifNotNull(mUserName, mPhotoUrl) {
userName, photoUrl ->
uploadPhoto(userName, photoUrl)
}
複製程式碼
這個函式定義程式碼:
fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
if (value1 != null && value2 != null) {
bothNotNull(value1, value2)
}
}
複製程式碼
5)使用Elvis操作符
Elvis操作符作用不錯在於如果你有空型別情況出現,會有返回值的功能。比如以下程式碼:
fun getUserName(): String {
if (mUserName != null) {
return mUserName!!
} else {
return "Anonymous"
}
}
複製程式碼
可以被替代如下程式碼:
fun getUserName(): String {
return mUserName ?: "Anonymous"
}
複製程式碼
6)按照你自己的宣告崩潰
儘管你知道型別是可空的,但是有些情況下你知道一些屬性是不可能為空的。一旦為空了,你應該很容易知道這是一個bug.然而拋棄使用 !! 非空斷言,系統就會給你丟擲一個很難去debug的通用的常 KotlinNullPointerException。使用內建函式 requireNotNull 或者 checkNotNull 和一些附帶的異常訊息易於除錯。類似如下程式碼:
uploadPhoto(intent.getStringExtra("PHOTO_URL")!!)
複製程式碼
以上程式碼可以替代為:
uploadPhoto(requireNotNull(intent.getStringExtra("PHOTO_URL"), { "Activity parameter 'PHOTO_URL' is missing" }))
複製程式碼
總結
如果你按照這6個提示,你可以從你的Kotlin程式碼刪除所有的 !! 非空斷言。這樣你的程式碼將更安全,更可除錯,更清潔。
譯者有話說
- 1、我為什麼要翻譯這篇部落格?
我們知道Kotlin中一個非常好的特性就是空型別安全的特性,也就是極大程度上避免了像Java中的空指標問題。是不是表示我使用Kotlin就不會存在空指標了呢。可以這麼說Kotlin空型別安全特性,對於會使用的人來說將會是非常方便和安全,對於不會使用的人來說(特別是一些初學者,包括還在用Java語言思想寫Kotlin程式碼的人)空型別安全特性的程式碼會寫得非常的ugly,例如譯文中反面教材例子濫用 !!非空斷言。可能不僅僅是程式碼醜陋的問題,還很容易帶來KotlinNullPointException. 如果你還在濫用!!(非空斷言)處理Kotlin中空型別的話,看完本篇部落格不妨嘗試一種優雅的方式去實現空型別安全
- 2、核心點提煉以及使用中需要注意的問題
第一對於儘量多的使用val替代var這個建議,我在之前部落格中多次提到。它可以避免出現一些不必要錯誤以及很好支援函數語言程式設計。
第二就是關於使用lateinit的問題,需要特別補充一點,當你在使用lateinit的時候,一定要保證你使用的這個屬性,必須要在它初始化之後使用。而且在開發中一個坑就是接收網路請求返回成功後的資料屬性不要用lateinit的修飾,因為由於某種異常情況,你的網路請求失敗,無法回撥到成功callback中,此時你的屬性沒有被初始化,而程式碼執行到使用這個屬性時候就會丟擲上面所說的UninitializedPropertyAccessException異常。建議使用lateinit屬性時,你非常清楚改屬性初始化是在使用之前,比如一般在onCreate方法中初始化的一些屬性就可以宣告成lateinit.
- 3、總結
關於空型別安全問題,其實還有很多需要注意的點,後續會有專門專題部落格來闡述Kotlin中空型別安全的問題。而這篇譯文則是先認識一下,以及在實際開發中如何優雅實現空型別安全的特性。
歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~