国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開(kāi)發(fā) > 綜合 > 正文

小談Kotlin的空處理的使用

2024-07-21 23:03:52
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

近來(lái)關(guān)于 Kotlin 的文章著實(shí)不少,Google 官方的支持讓越來(lái)越多的開(kāi)發(fā)者開(kāi)始關(guān)注 Kotlin。不久前加入的項(xiàng)目用的是 Kotlin 與 Java 混合開(kāi)發(fā)的模式,紙上得來(lái)終覺(jué)淺,終于可以實(shí)踐一把新語(yǔ)言。 本文就來(lái)小談一下 Kotlin 中的空處理。

一、上手的確容易

先扯一扯 Kotlin 學(xué)習(xí)本身。

之前各種聽(tīng)人說(shuō)上手容易,但真要切換到另一門語(yǔ)言,難免還是會(huì)躊躇是否有這個(gè)必要。現(xiàn)在因?yàn)楣ぷ麝P(guān)系直接上手 Kotlin,感受是 真香(上手的確容易) 。

首先在代碼閱讀層面,對(duì)于有 Java 基礎(chǔ)的程序員來(lái)說(shuō)閱讀 Kotlin 代碼基本無(wú)障礙,除去一些操作符、一些順序上的變化,整體上可以直接閱讀。

其次在代碼編寫層面,僅需要改變一些編碼習(xí)慣。主要是:語(yǔ)句不要寫分號(hào)、變量需要用 var 或 val 聲明、類型寫在變量之后、實(shí)例化一個(gè)對(duì)象時(shí)不用 “new” …… 習(xí)慣層面的改變只需要多寫代碼,自然而然就適應(yīng)了。

最后在學(xué)習(xí)方式層面,由于 Kotlin 最終都會(huì)被編譯成字節(jié)碼跑在 JVM 上,所以初入手時(shí)完全可以用 Java 作為對(duì)比。比如你可能不知道 Kotlin 里 companion object 是什么意思,但你知道既然 Kotlin 最終會(huì)轉(zhuǎn)成 jvm 可以跑的字節(jié)碼,那 Java 里必然可以找到與之對(duì)應(yīng)的東西。

Android Studio 也提供了很方便的工具。選擇菜單 Tools -> Kotlin -> Show Kotlin Bytecode 即可看到 Kotlin 編譯成的字節(jié)碼,點(diǎn)擊窗口上方的 “Decompile” 即可看到這份字節(jié)碼對(duì)應(yīng)的 Java 代碼。—— 這個(gè)工具特別重要,假如一段 Kotlin 代碼讓你看得云里霧里,看一下它對(duì)應(yīng)的 Java 代碼你就能知道它的含義。

當(dāng)然這里僅僅是說(shuō)上手或入門(僅入門的話可以忽略諸如協(xié)程等高級(jí)特性),真正熟練應(yīng)用乃至完全掌握肯定需要一定時(shí)間。

二、針對(duì) NPE 的強(qiáng)規(guī)則

有些文章說(shuō) Kotlin 幫開(kāi)發(fā)者解決了 NPE(NullPointerException),這個(gè)說(shuō)法是不對(duì)的。 在我看來(lái),Kotlin 沒(méi)有幫開(kāi)發(fā)者解決了 NPE (Kotlin: 臣妾真的做不到啊),而是通過(guò)在語(yǔ)言層面增加各種強(qiáng)規(guī)則,強(qiáng)制開(kāi)發(fā)者去自己處理可能的空指針問(wèn)題,達(dá)到盡量減少(只能減少而無(wú)法完全避免)出現(xiàn) NPE 的目的。

那么 Kotlin 具體是怎么做的呢?別著急,我們可以先回顧一下在 Java 中我們是怎么處理空指針問(wèn)題的。

Java 中對(duì)于空指針的處理總體來(lái)說(shuō)可以分為“防御式編程”和“契約式編程”兩種方案。

“防御式編程”大家應(yīng)該不陌生,核心思想是不信任任何“外部”輸入 —— 不管是真實(shí)的用戶輸入還是其他模塊傳入的實(shí)參,具體點(diǎn)就是 各種判空 。創(chuàng)建一個(gè)方法需要判空,創(chuàng)建一個(gè)邏輯塊需要判空,甚至自己的代碼內(nèi)部也需要判空(防止對(duì)象的回收之類的)。示例如下:

public void showToast(Activity activity) {  if (activity == null) {    return;  }    ......}

另一種是“契約式編程”,各個(gè)模塊之間約定好一種規(guī)則,大家按照規(guī)則來(lái)辦事,出了問(wèn)題找沒(méi)有遵守規(guī)則的人負(fù)責(zé),這樣可以避免大量的判空邏輯。Android 提供了相關(guān)的注解以及最基礎(chǔ)的檢查來(lái)協(xié)助開(kāi)發(fā)者,示例如下:

public void showToast(@NonNull Activity activity) {  ......}

在示例中我們給 Activity 增加了 @NonNull 的注解,就是向所有調(diào)用這個(gè)方法的人聲明了一個(gè)約定,調(diào)用方應(yīng)該保證傳入的 activity 非空。當(dāng)然聰明的你應(yīng)該知道,這是一個(gè)很弱的限制,調(diào)用方?jīng)]注意或者不理會(huì)這個(gè)注解的話,程序就依然還有 NPE 導(dǎo)致的 crash 的風(fēng)險(xiǎn)。

回過(guò)頭來(lái), 對(duì)于 Kotlin,我覺(jué)得就是一種把契約式編程和防御式編程相結(jié)合且提升到語(yǔ)言層面的處理方式。 (聽(tīng)起來(lái)似乎比 Java 中各種判空或注解更麻煩?繼續(xù)看下去,你會(huì)發(fā)現(xiàn)的確是更麻煩……)

在 Kotlin 中,有以下幾方面約束:

在聲明階段,變量需要決定自己是否可為空,比如 var time: Long? 可接受 null,而 var time: Long 則不能接受 null。

在變量傳遞階段,必須保持“可空性”一致,比如形參聲明是不為空的,那么實(shí)參必須本身是非空或者轉(zhuǎn)為非空才能正常傳遞。示例如下:

fun main() {    ......    // test(isOpen) 直接這樣調(diào)用,編譯不通過(guò)    // 可以是在空檢查之內(nèi)傳遞,證明自己非空    isOpen?.apply {       test(this)    }    // 也可以是強(qiáng)制轉(zhuǎn)成非空類型    test(isOpen!!)  }    private fun test(open: Boolean) {    ......  }

在使用階段,需要嚴(yán)格判空:

var time: Long? = 1000   //盡管你才賦值了非空的值,但在使用過(guò)程中,你無(wú)法這樣:   //time.toInt()   //必須判空   time?.toInt()

總的來(lái)說(shuō) Kotlin 為了解決 NPE 做了大量語(yǔ)言層級(jí)的強(qiáng)限制,的確可以做到減少 NPE 的發(fā)生。但這種既“契約式”(判空)又“防御式”(聲明空與非空)的方案會(huì)讓開(kāi)發(fā)者做更多的工作,會(huì)更“麻煩”一點(diǎn)。

當(dāng)然,Kotlin 為了減少麻煩,用 “?” 簡(jiǎn)化了判空邏輯 —— “?” 的實(shí)質(zhì)還是判空,我們可以通過(guò)工具查看 time?.toInt() 的 Java 等價(jià)代碼是:

if (time != null) {  int var10000 = (int)time;}

這種簡(jiǎn)化在數(shù)據(jù)層級(jí)很深需要寫大量判空語(yǔ)句時(shí)會(huì)特別方便,這也是為什么 雖然邏輯上 Kotlin 讓開(kāi)發(fā)者做了更多工作,但寫代碼過(guò)程中卻并沒(méi)有感覺(jué)到更麻煩。

三、強(qiáng)規(guī)則之下的 NPE 問(wèn)題

在 Kotlin 這么嚴(yán)密的防御之下,NPE 問(wèn)題是否已經(jīng)被終結(jié)了呢?答案當(dāng)然是否定的。在實(shí)踐過(guò)程中我們發(fā)現(xiàn)主要有以下幾種容易導(dǎo)致 NPE 的場(chǎng)景:

1. data class(含義對(duì)應(yīng) Java 中的 model)聲明了非空

例如從后端拿 json 數(shù)據(jù)的場(chǎng)景,后端的哪個(gè)字段可能會(huì)傳空是客戶端無(wú)法控制的,這種情況下我們的預(yù)期 必須是 每個(gè)字段都可能為空,這樣轉(zhuǎn)成 json object 時(shí)才不會(huì)有問(wèn)題:

data class User(    var id: Long?,    var gender: Long?,    var avatar: String?)

假如有一個(gè)字段忘了加上”?”,后端沒(méi)傳該值就會(huì)拋出空指針異常。

2. 過(guò)分依賴 Kotlin 的空值檢查

private lateinit var mUser: User...private fun initView() { mUser = intent.getParcelableExtra<User>("key_user")}

在 Kotlin 的體系中久了會(huì)過(guò)分依賴于 Android Studio 的空值檢查,在代碼提示中 Intent 的 getParcelableExtra 方法返回的是非空,因此這里你直接用方法結(jié)果賦值不會(huì)有任何警告。但點(diǎn)擊進(jìn) getParcelableExtra 方法內(nèi)部你會(huì)發(fā)現(xiàn)它的實(shí)現(xiàn)是這樣的:

public <T extends Parcelable> T getParcelableExtra(String name) {    return mExtras == null ? null : mExtras.<T>getParcelable(name);  }

內(nèi)部的其他代碼不展開(kāi)了,總之它是可能會(huì)返回 null 的,直接賦值顯然會(huì)有問(wèn)題。

我理解這是 Kotlin 編譯工具對(duì) Java 代碼檢查的不足之處, 它無(wú)法準(zhǔn)確判斷 Java 方法是否會(huì)返回空就選擇無(wú)條件信任,即便方法本身可能還聲明了 @Nullable 。

3. 變量或形參聲明為非空

這點(diǎn)與第一、第二點(diǎn)都很類似,主要是使用過(guò)程中一定要進(jìn)一步思考傳遞過(guò)來(lái)的值是否真的非空。

有人可能會(huì)說(shuō),那我全部都聲明為可空類型不就得了么 —— 這樣做會(huì)讓你在使用該變量的所有地方都需要判空,Kotlin 本身的便利性就蕩然無(wú)存了。

我的觀點(diǎn)是不要因噎廢食,使用時(shí)多注意點(diǎn)就可以避免大部分問(wèn)題。

4. !! 強(qiáng)行轉(zhuǎn)為非空

當(dāng)將可空類型賦值給非空類型時(shí),需要有對(duì)空類型的判斷,確保非空才能賦值(Kotlin 的約束)。

我們使用 !! 可以很方便得將“可空”轉(zhuǎn)為“非空”, 但可空變量值為 null,則會(huì) crash 。

因此使用上建議在確保非空時(shí)才用 !! :

param!!

否則還是盡量放在判空代碼塊里:

param?.let { doSomething(it) }

四、實(shí)踐中碰到的問(wèn)題

從 Java 的空處理轉(zhuǎn)到 Kotlin 的空處理,我們可能會(huì)下意識(shí)去尋找對(duì)標(biāo) Java 的判空寫法:

if (n != null) { //非空如何 } else { //為空又如何}

在 Kotlin 中類似的寫法的確有,那就是結(jié)合高階函數(shù) let、apply、run …… 來(lái)處理判空,比如上述 Java 代碼就可以寫成:

n?.let { //非空如何} ?: let { //為空又如何}

但這里有幾個(gè)小坑。

1. 兩個(gè)代碼塊不是互斥關(guān)系

假如是 Java 的寫法,那么不管 n 的值怎樣,兩個(gè)代碼塊都是互斥的,也就是“非黑即白”。但 Kotlin 的這種寫法不是(不確定這種寫法是否是最佳實(shí)踐,假如有更好的方案可以留言指出)。

?: 這個(gè)操作符可以理解為 if (a != null) a else b ,也就是它之前的值非空返回之前的值,否則返回之后的值。

而上面代碼中這些高階函數(shù)都是有返回值的,詳見(jiàn)下表:

 

函數(shù) 返回值
let 返回指定 return 或函數(shù)里最后一行
apply 返回該對(duì)象本身
run 返回指定 return 或函數(shù)里最后一行
with 返回指定 return 或函數(shù)里最后一行
also 返回該對(duì)象本身
takeIf 條件成立返回對(duì)象本身,不成立返回 null
takeUnless 條件成立返回 null,不成立返回該對(duì)象本身

 

假如用的是 let, 注意看它的返回值是“指定 return 或函數(shù)里最后一行”,那么碰到以下情況:

val n = 1var a = 0n?.let { a++ ... null //最后一行為 null} ?: let { a++}

你會(huì)很神奇地發(fā)現(xiàn) a 的值是 2,也就是 既執(zhí)行了前一個(gè)代碼塊,也執(zhí)行了后一個(gè)代碼塊 。

上面這種寫法你可能不以為然,因?yàn)楹苊黠@地提醒了諸位需要注意最后一行,但假如是之前沒(méi)注意這個(gè)細(xì)節(jié)或者是下面這種寫法呢?

n?.let { ... anMap.put(key, value) // anMap 是一個(gè) HashMap} ?: let { ...}

應(yīng)該很少人會(huì)注意到 Map 的 put 方法是有返回值的,且可能會(huì)返回 null。那么這種情況下很容易踩坑。

2. 兩個(gè)代碼塊的對(duì)象不同

以 let 為例,在 let 代碼塊里可以用 it 指代該對(duì)象(其他高階函數(shù)可能用 this,類似的),那么我們?cè)趯懭缦麓a時(shí)可能會(huì)順手這樣寫:

activity { n?.let { it.hashCode() // it 為 n } ?: let { it.hashCode() // it 為 activity } }

結(jié)果自然會(huì)發(fā)現(xiàn)值不一樣。前一個(gè)代碼塊 it 指代的是 n,而后一個(gè)代碼塊里 it 指代的是整個(gè)代碼塊指向的 this。

原因是 ?: 與 let 之間是沒(méi)有 . 的,也就是說(shuō) 后一個(gè)代碼塊調(diào)用 let 的對(duì)象并不是被判空的對(duì)象,而是 this 。(不過(guò)這種場(chǎng)景會(huì)出錯(cuò)的概率不大,因?yàn)樵诤笠粋€(gè)代碼塊里很多對(duì)象 n 的方法用不了,就會(huì)注意到問(wèn)題了)

后記

總的來(lái)說(shuō)切換到 Kotlin 還是比預(yù)期順利和舒服,寫慣了 Kotlin 后再回去寫 Java 反倒有點(diǎn)不習(xí)慣。今天先寫這點(diǎn),后面有其他需要總結(jié)的再分享。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到kotlin教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 萨嘎县| 宝丰县| 民丰县| 营山县| 广昌县| 余姚市| 湖州市| 黔江区| 南郑县| 广水市| 淮南市| 玛沁县| 云梦县| 阿巴嘎旗| 高邑县| 定边县| 淄博市| 襄垣县| 承德县| 高陵县| 防城港市| 丰县| 大丰市| 五华县| 易门县| 白城市| 泸西县| 黄龙县| 垫江县| 治多县| 吴忠市| 唐海县| 漳浦县| 孟村| 吉木乃县| 南靖县| 拜泉县| 错那县| 达日县| 师宗县| 百色市|