iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 3
0
Mobile Development

大一之 Android Kotlin 自習心路歷程系列 第 3

[Day 3] Android in Kotlin: NonNull and Nullable

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()
}

想要解決上述問題,提示也有寫,可以用兩種 「?.」 或是 「!!.」。

問號和驚嘆號

Non-null 「!!.」

var name: String? = "Jack"
textView.text= name!!

要是你很確定你的資料不是 null,可以加上「!!」跟編譯器說:我一定會有值、不會是 null,請你執行。

變成底下這樣

holder.tvDescr.text= viewModel.pageDateData.value!![position].descr

但是,「!!」筆者認為還是少用為妙,原因很簡單,因為是強制執行的,當有地方沒有處理好,很容易就出現絕望的 NPE——NullPointerException,明顯不是一個很安全的解決方式。

所以,最好是將型態改為 nullable,不然的話會比較推薦用下面那個方法

Null-safe 「?.」

在這裡的「?」是指「如果不為空就執行」,也就是做濾空。
我認為這才是較為安全的做法,畢竟如果真的出了什麼意外,他是個空值,那也就大不了不執行。

當然還是要依照當下情況決定啦。

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 的判斷句,使得外觀更加簡潔。


上一篇
[Day 2] Android in Kotlin: 上手 Kotlin 的簡易開始
下一篇
[Day 4] Android in Kotlin: Object 與 Singleton
系列文
大一之 Android Kotlin 自習心路歷程30

尚未有邦友留言

立即登入留言