kotlin 的特別之處其中之一就是他對於 null 的處理相當嚴格,這當然有好有壞,好處是在處理得當的情況之下,比起 Java 出現較少的 Nullpointer 例外。在剛開始用的時候會覺得 kotlin 很囉唆,一樣是使用時間久了就會習慣了。
以下看例子。
var name: String
像上面這樣子正常的在 class 裡面宣告一個字串成員,但是卻會報出錯誤
意思是指這個「name」的型態是 「non-null 型態的 String」,不可以為空,也就是不能是 null
所以,修正方法一就是加上初值
var name: String= ""
但如果就是想要他是 null 的話,可以在資料型態的後面加上 「?」,使其型態為 「String?」,意思是指從「non-null 型態的 String」變成「nullable 型態的 String」
var name: String?= null
將他們反編譯成 java ,就可以看到他們分別被加上 @NonNull 跟 @Nullable 的註解
當然除了 String 以外,其他資料型態也可以
var id: Int? = null
而這就是問號的第一個意思——可以為空
然而,就因為他是可以為空的情況,如果使用的地方是在「一定不能為空」的地方的話,他就會報錯。以筆者來說,最近最常遇到的地方就是 LiveData 了。
LiveData 的 getter 在 LiveData.java 中是這樣子寫的
@Nullable
public T getValue() {
Object data = mData;
if (data != NOT_SET) {
return (T) data;
}
return null;
}
從上面的 code 可以很明顯的發現,他是有可能會回傳 null 的。那在使用上,如果那個地方是 @NonNull 的話,就必須要做處理。
比如說,我想要在 recyclerview adpater 中取出 viewModel 從 repository 拿到資料庫裡的 Live data
但我那個資料庫的 Entity 所放的資料都是沒有加 ? 的 NonNull 型態
data class ExpenseEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Int,
@ColumnInfo(name = "amount") var amount: Double,
@ColumnInfo(name = "date") var date: String,
@ColumnInfo(name = "descr") var descr: String,
@ColumnInfo(name = "account_id") var accountId: Int,
@ColumnInfo(name = "category_id") var categoryId: Int,
@ColumnInfo(name = "routine_id") var routineId: Int
)
那在 Recycler view adapter 做直接取出就會發生問題:
(只保留部份程式碼)
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.tvDescr.text= viewModel.pageDateData.value[position].descr
holder.tvDate.text= viewModel.pageDateData.value[position].date
holder.tvAmount.text= viewModel.pageDateData.value[position].amount.toString()
}
想要解決上述問題,提示也有寫,可以用兩種 「?.」 或是 「!!.」。
var name: String? = "Jack"
textView.text= name!!
要是你很確定你的資料不是 null,可以加上「!!」跟編譯器說:我一定會有值、不會是 null,請你執行。
變成底下這樣
holder.tvDescr.text= viewModel.pageDateData.value!![position].descr
但是,「!!」筆者認為還是少用為妙,原因很簡單,因為是強制執行的,當有地方沒有處理好,很容易就出現絕望的 NPE——NullPointerException,明顯不是一個很安全的解決方式。
所以,最好是將型態改為 nullable,不然的話會比較推薦用下面那個方法
在這裡的「?」是指「如果不為空就執行」,也就是做濾空。
我認為這才是較為安全的做法,畢竟如果真的出了什麼意外,他是個空值,那也就大不了不執行。
當然還是要依照當下情況決定啦。
holder.tvDescr.text= viewModel.pageDateData.value?[position].descr
「?.」的 safe calls 還可以廣泛的用在濾空
舉 kotlin 官網的例子:
val l = b?.length ?: -1
意思等同於以下
val l: Int =
if (b != null) {
b.length
} else {
-1
}
當 b 是 null 時,則回傳一
問號是一個在 kotlin 中較為常用的濾空用法,比如我想要在 name 不為空時執行,可以這麼做
name?.let{
textView.text= it
}
他減少了 null 的判斷句,使得外觀更加簡潔。