1. 程式人生 > >Kotlin入門(8)空值的判斷與處理

Kotlin入門(8)空值的判斷與處理

上一篇文章介紹瞭如何對迴圈語句進行操作,末尾還演示了發現空串時直接繼續下一迴圈,只是在初始化字串陣列時使用了“val poem2Array:Array<String?> = ***”,該表示式不免令人疑惑,為何這裡要在String後面加個問號?由此,本文就Kotlin如何判斷和處理空值,再做進一步的深入探討。

以往的開發工作之中,少不了要跟各種異常作鬥爭,常見的異常種類包括空指標異常NullPointerException、陣列越界異常IndexOutOfBoundsException、型別轉換異常ClassCastException等等,其中最讓人頭痛的當數空指標異常,該異常頻繁發生卻又隱藏很深。呼叫一個空物件的方法,就會產生空指標異常,可是Java編碼的時候編譯器不會報錯,開發者通常也意識不到問題,只有App執行之時發生閃退,檢視崩潰日誌才會恍然大悟“原來這裡得加上物件非空的判斷”。然而,饒是有經驗的開發者,尚且擺脫不了如影隨形的空指標,何況程式設計新手呢?問題的癥結在於,Java編譯器不會檢查空值,只能由開發者在程式碼中增加“if (*** != null)”的判斷,但是業務程式碼裡面的方法呼叫浩如繁星,倘若在每個方法呼叫之前都加上非空判斷,勢必大量程式碼都充滿了“if (*** != null)”,這樣做的後果不僅降低了程式碼的可讀性,而且給開發者帶來不少的額外工作量。

空指標只是狹義上的空值,廣義上的空值除了空指標,還包括其它開發者認可的情況。比如說String型別,字串的長度為0時也可算是空值;如果字串的內容全部由空格組成,某種意義上也是空值。那麼字串的非空判斷,用Java書寫的話見下面示例程式碼:
    if (str!=null && str.length()>0 && str.trim().length()>0) {
        ......
    }
可以看到以上的非空判斷語句有點冗長了,因此作為開發者,必須把會被多次呼叫的程式碼封裝成工具類。既然大家都這麼想,Android系統的研發工程師也不例外,所以安卓的SDK已經提供了TextUtils.isEmpty(***)這個公共方法,專門用於校驗某個字串是否為空值。Kotlin的研發人員當然不會放過這點,就像讀者在上一篇文章中看到的那樣,Kotlin通過isNullOrBlank函式進行空值校驗,下面列出Kotlin校驗字串空值的幾個方法:
isNullOrEmpty : 為空指標或者字串長度為0時返回true,非空串與可空串均可呼叫。
isNullOrBlank : 為空指標或者字串長度為0或者全為空格時返回true,非空串與可空串均可呼叫。
isEmpty : 字串長度為0時返回true,只有非空串可呼叫。
isBlank : 字串長度為0或者全為空格時返回true,只有非空串可呼叫。
isNotEmpty : 字串長度大於0時返回true,只有非空串可呼叫。
isNotBlank : 字串長度大於0且不是全空格串時返回true,只有非空串可呼叫。

注意到上面的方法有區分非空串與可空串,這是緣於Kotlin引入了空安全的概念,每個型別的物件都分作不可為null和可以為null兩種。前面的文章中,正常宣告的物件預設都是非空(不可為null),比如下面這個宣告字串變數的程式碼
    var strNotNull:String = ""
非空物件要麼在宣告時就賦值,要麼在方法呼叫前賦值;否則未經初始化就呼叫該物件的方法,Kotlin會像語法錯誤那樣提示這裡“Variable *** must be initialized”。至於可以為空的物件,可於宣告之時在型別後面加個問號,如同上一篇文章宣告可空字串陣列的程式碼“val poem2Array:Array<String?> = ***”,只宣告一個可空字串物件的程式碼如下所示:
    var strCanNull:String?
現在有了兩個字串,其中strNotNull為非空串,strCanNull為可空串。按照前面幾個字串空值校驗方法的規則,strNotNull允許呼叫全部六個方法,但strCanNull只允許呼叫isNullOrEmpty和isNullOrBlank兩個方法。因為strCanNull可能為空指標,若去呼叫一個空指標物件的length方法,毫無疑問會扔出空指標異常,所以Kotlin對可空串增加了編譯檢查,一旦發現某個可空串呼叫isEmpty/isBlank/isNotEmpty/isNotBlank,立刻提示此處語法錯誤“Only *** calls are allowed on a nullable receiver of type String”。

可是上述的幾個方法侷限於判斷字串是否為空串,如果要求獲得字串的長度,或者呼叫其它物件型別的方法,仍然要判斷空指標。以獲取字串長度為例,下面聲明瞭三個字串物件,其中strA為非空串,strB和strC都是可空串,不過strB為空而strC實際有值,字串物件的宣告程式碼如下:
    val strA:String = "非空"
    val strB:String? = null
    val strC:String? = "可空串"
對於strA,因為它是非空串,所以可直接獲取length長度屬性。對於strB和strC,必須進行非空判斷,否則編譯器會提示該行程式碼存在錯誤。具體的長度獲取程式碼如下所示:
    var length:Int = 0
    btn_length_a.setOnClickListener { length=strA.length; tv_check_result.text="字串A的長度為$length" }
    btn_length_b.setOnClickListener {
        //length=strB.length //這種寫法是不行的,因為strB可能為空,會扔出空指標異常
        length = if (strB!=null) strB.length else -1
        tv_check_result.text="字串B的長度為$length"
    }
    btn_length_c.setOnClickListener {
        //即使strC實際有值,也必須做非空判斷,誰叫它號稱可空呢?編譯器寧可錯殺一千、不可放過一個
        length = if (strC!=null) strC.length else -1
        tv_check_result.text = "字串C的長度為$length"
    }
以上的if/else雖然已經完成非空判斷的功能,可是Kotlin仍舊嫌它太囉嗦,中國人把繁體字簡化為簡體字,外國人也想辦法簡化程式語言,中外人士果然所見略同。原本直接獲取可空串的length屬性會扔出空指標異常,那就加個標記,遇到空指標別扔異常,直接返回空指標就好了,至少避免了處理異常的麻煩事。具體的標記程式碼如下:
    var length_null:Int?
    btn_question_dot.setOnClickListener {
        //?.表示物件為空時就直接返回null,所以返回值的變數必須被宣告為可空型別
        length_null = strB?.length
        tv_check_result.text = "使用?.得到字串B的長度為$length_null"
    }
從程式碼中可以看出,這個多出來的標記是個問號,語句“strB?.length”等價於“length_null = if (strB!=null) strB.length else null”。但是,該語句意味著返回值仍然可能為空,如果不想在介面上展示“null”,還得另外判斷length_null是否為空;也就是說,這個做法並未實現與原始碼完全一致的功能。


沒有完成任務,Kotlin當然不會罷休,所以它又引入了一個運算子“?:”,學名叫做“Elvis 操作符”,叫起來有點拗口,讀者可以把它當作是Java三元運算子“變數名=條件語句?取值A:取值B”的縮寫。引入“?:”的實現程式碼如下所示:
    btn_question_colon.setOnClickListener {
        //?:表示為空時就返回右邊的值,即(x!=null)?x.**:y
        length = strB?.length?: -1
        tv_check_result.text = "使用?:得到字串B的長度為$length"
    }
這樣總該完事了吧?然而執拗的Kotlin攻城獅覺得還是囉嗦,因為經常上一行程式碼就對strB賦值了,所以此時可以百分百保證strB非空,那又何必浪費口舌呢?於是Kotlin另外引入了運算子“!!”,表示甭管那麼多了,前方沒有地雷,弟兄們趕緊上!下面是“!!”的運用程式碼例子:
    btn_exclamation_two.setOnClickListener {
        strB = "排雷完畢"
        length = strB!!.length
        tv_check_result.text = "使用?:得到字串B的長度為$length"
    }
既然運算子“!!”強行放棄了非空判斷,開發者就得自己注意排雷了。否則的話,一旦出現空指標,App執行時依然會丟擲異常。以下的演示程式碼在執行時會扔出空指標異常,故而增加了異常捕獲處理:
    btn_exclamation_two.setOnClickListener {
        //!!表示不做非空判斷,強制執行後面的表示式,如果物件為空就會扔出空異常
        //所以只有在確保為非空時,才能使用!!
        try {
            //即使返回給可空變數length_null,也會扔出異常
            length = strB!!.length
            tv_check_result.text = "使用!!得到字串B的長度為$length"
        } catch(e:Exception) {
            tv_check_result.text = "發現空指標異常"
        }
    }

總結一下,Kotlin引入了空安全的概念,並在編譯時開展物件是否為空的校驗。相關的操作符說明概括如下:
1、宣告物件例項時,在型別名稱後面加問號,表示該物件可以為空;
2、呼叫物件方法時,在例項名稱後面加問號,表示一旦例項為空就返回null;
3、新引入運算子“?:”,一旦例項為空就返回該運算子右邊的表示式;
4、新引入運算子“!!”,通知編譯器不做非空校驗,執行時一旦發現例項為空就扔出異常;





__________________________________________________________________________
本文現已同步釋出到微信公眾號“老歐說安卓”,開啟微信掃一掃下面的二維碼,或者直接搜尋公眾號“老歐說安卓”新增關注,更快更方便地閱讀技術乾貨。