十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂(yōu)售后,網(wǎng)站問(wèn)題一站解決
這篇文章給大家分享的是有關(guān)Kotlin中空處理怎么用的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

一、上手的確容易
先扯一扯 Kotlin 學(xué)習(xí)本身。
之前各種聽(tīng)人說(shuō)上手容易,但真要切換到另一門(mén)語(yǔ)言,難免還是會(huì)躊躇是否有這個(gè)必要?,F(xiàn)在因?yàn)楣ぷ麝P(guān)系直接上手 Kotlin,感受是 真香(上手的確容易) 。
首先在代碼閱讀層面,對(duì)于有 Java 基礎(chǔ)的程序員來(lái)說(shuō)閱讀 Kotlin 代碼基本無(wú)障礙,除去一些操作符、一些順序上的變化,整體上可以直接閱讀。
其次在代碼編寫(xiě)層面,僅需要改變一些編碼習(xí)慣。主要是:語(yǔ)句不要寫(xiě)分號(hào)、變量需要用 var 或 val 聲明、類(lèi)型寫(xiě)在變量之后、實(shí)例化一個(gè)對(duì)象時(shí)不用 “new” …… 習(xí)慣層面的改變只需要多寫(xiě)代碼,自然而然就適應(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ō)上手或入門(mén)(僅入門(mén)的話(huà)可以忽略諸如協(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í)的用戶(hù)輸入還是其他模塊傳入的實(shí)參,具體點(diǎn)就是 各種判空 。創(chuàng)建一個(gè)方法需要判空,創(chuàng)建一個(gè)邏輯塊需要判空,甚至自己的代碼內(nèi)部也需要判空(防止對(duì)象的回收之類(lèi)的)。示例如下:
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è)注解的話(huà),程序就依然還有 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)成非空類(lèi)型
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í)很深需要寫(xiě)大量判空語(yǔ)句時(shí)會(huì)特別方便,這也是為什么 雖然邏輯上 Kotlin 讓開(kāi)發(fā)者做了更多工作,但寫(xiě)代碼過(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ì)傳空是客戶(hù)端無(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ò)分依賴(lài) Kotlin 的空值檢查
private lateinit var mUser: User
...
private fun initView() {
mUser = intent.getParcelableExtra("key_user")
} 在 Kotlin 的體系中久了會(huì)過(guò)分依賴(lài)于 Android Studio 的空值檢查,在代碼提示中 Intent 的 getParcelableExtra 方法返回的是非空,因此這里你直接用方法結(jié)果賦值不會(huì)有任何警告。但點(diǎn)擊進(jìn) getParcelableExtra 方法內(nèi)部你會(huì)發(fā)現(xiàn)它的實(shí)現(xiàn)是這樣的:
publicT getParcelableExtra(String name) { return mExtras == null ? null : mExtras. 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)都很類(lèi)似,主要是使用過(guò)程中一定要進(jìn)一步思考傳遞過(guò)來(lái)的值是否真的非空。
有人可能會(huì)說(shuō),那我全部都聲明為可空類(lèi)型不就得了么 —— 這樣做會(huì)讓你在使用該變量的所有地方都需要判空,Kotlin 本身的便利性就蕩然無(wú)存了。
我的觀(guān)點(diǎn)是不要因噎廢食,使用時(shí)多注意點(diǎn)就可以避免大部分問(wèn)題。
4. !! 強(qiáng)行轉(zhuǎn)為非空
當(dāng)將可空類(lèi)型賦值給非空類(lèi)型時(shí),需要有對(duì)空類(lèi)型的判斷,確保非空才能賦值(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 的判空寫(xiě)法:
if (n != null) {
//非空如何
} else {
//為空又如何
}在 Kotlin 中類(lèi)似的寫(xiě)法的確有,那就是結(jié)合高階函數(shù) let、apply、run …… 來(lái)處理判空,比如上述 Java 代碼就可以寫(xiě)成:
n?.let {
//非空如何
} ?: let {
//為空又如何
}但這里有幾個(gè)小坑。
1. 兩個(gè)代碼塊不是互斥關(guān)系
假如是 Java 的寫(xiě)法,那么不管 n 的值怎樣,兩個(gè)代碼塊都是互斥的,也就是“非黑即白”。但 Kotlin 的這種寫(xiě)法不是(不確定這種寫(xiě)法是否是最佳實(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 = 1
var a = 0
n?.let {
a++
...
null //最后一行為 null
} ?: let {
a++
}你會(huì)很神奇地發(fā)現(xiàn) a 的值是 2,也就是 既執(zhí)行了前一個(gè)代碼塊,也執(zhí)行了后一個(gè)代碼塊 。
上面這種寫(xiě)法你可能不以為然,因?yàn)楹苊黠@地提醒了諸位需要注意最后一行,但假如是之前沒(méi)注意這個(gè)細(xì)節(jié)或者是下面這種寫(xiě)法呢?
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,類(lèi)似的),那么我們?cè)趯?xiě)如下代碼時(shí)可能會(huì)順手這樣寫(xiě):
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)題了)
感謝各位的閱讀!關(guān)于“Kotlin中空處理怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!