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、新引入運算符“!!”,通知編譯器不做非空校驗,運行時一旦發現實例爲空就扔出異常;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章