欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

小談Kotlin的空處理的使用

 更新時(shí)間:2019年01月21日 11:13:30   作者:Zhenghui''s World  
這篇文章主要介紹了小談Kotlin的空處理的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

近來(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ō)上手容易,但真要切換到另一門(mén)語(yǔ)言,難免還是會(huì)躊躇是否有這個(gè)必要。現(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)的話可以忽略諸如協(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è)注解的話,程序就依然還有 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<User>("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)是這樣的:

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)都很類(lèi)似,主要是使用過(guò)程中一定要進(jìn)一步思考傳遞過(guò)來(lái)的值是否真的非空。

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

我的觀點(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)題了)

后記

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

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

相關(guān)文章

最新評(píng)論