在Java中如何優(yōu)雅地判空

判空災(zāi)難

作為搬磚黨的一族們,我們對(duì)判空一定再熟悉不過了岳遥,不要跟我說你很少進(jìn)行判空窄赋,除非你喜歡NullPointerException。

不過NullPointerException對(duì)于很多猿們來說矩距,也是Exception家族中最親近的一員了。

為了避免NullPointerException來找我們怖竭,我們經(jīng)常會(huì)進(jìn)行如下操作锥债。

if(data!=null)?{

dosth.

}

如果一個(gè)類中多次使用某個(gè)對(duì)象,那你可能要一頓操作痊臭,so:

“世界第九大奇跡”就這樣誕生了哮肚。Maybe你會(huì)想,項(xiàng)目中肯定不止你一個(gè)人會(huì)這樣一頓操作广匙,然后按下Command+Shift+F允趟,真相就在眼前:

What,我們有接近一萬行的代碼都是在判空鸦致?

好了潮剪,接下來涣楷,要進(jìn)入正題了。

NullObject模式

對(duì)于項(xiàng)目中無數(shù)次的判空抗碰,對(duì)代碼質(zhì)量整潔度產(chǎn)生了十分之惡劣的影響狮斗,對(duì)于這種現(xiàn)象,我們稱之為“判空災(zāi)難”弧蝇。

那么碳褒,這種現(xiàn)象如何治理呢,你可能聽說過NullObject模式捍壤,不過這不是我們今天的武器骤视,但是還是需要介紹一下NullObject模式。

什么是NullObject模式呢鹃觉?

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof).

以上解析來自Wikipedia专酗。

NullObject模式首次發(fā)表在“ 程序設(shè)計(jì)模式語(yǔ)言 ”系列叢書中。一般的盗扇,在面向?qū)ο笳Z(yǔ)言中祷肯,對(duì)對(duì)象的調(diào)用前需要使用判空檢查,來判斷這些對(duì)象是否為空疗隶,因?yàn)樵诳找蒙蠠o法調(diào)用所需方法佑笋。

空對(duì)象模式的一種典型實(shí)現(xiàn)方式如下圖所示(圖片來自網(wǎng)絡(luò)):

示例代碼如下(命名來自網(wǎng)絡(luò),哈哈到底是有多懶):

Nullable是空對(duì)象的相關(guān)操作接口斑鼻,用于確定對(duì)象是否為空蒋纬,因?yàn)樵诳諏?duì)象模式中,對(duì)象為空會(huì)被包裝成一個(gè)Object坚弱,成為Null Object蜀备,該對(duì)象會(huì)對(duì)原有對(duì)象的所有方法進(jìn)行空實(shí)現(xiàn)…

publicinterfaceNullable{

booleanisNull();

}

這個(gè)接口定義了業(yè)務(wù)對(duì)象的行為。

publicinterfaceDependencyBaseextendsNullable{

voidOperation();

}

這是該對(duì)象的真實(shí)類荒叶,實(shí)現(xiàn)了業(yè)務(wù)行為接口DependencyBase與空對(duì)象操作接口Nullable碾阁。

publicclassDependencyimplementsDependencyBase,Nullable{

@Override

publicvoidOperation(){

System.out.print("Test!");

}

@Override

publicbooleanisNull(){

returnfalse;

}

}

這是空對(duì)象,對(duì)原有對(duì)象的行為進(jìn)行了空實(shí)現(xiàn)些楣。

publicclassNullObjectimplementsDependencyBase{

@Override

publicvoidOperation(){

//?do?nothing

}

@Override

publicbooleanisNull(){

returntrue;

}

}

在使用時(shí)脂凶,可以通過工廠調(diào)用方式來進(jìn)行空對(duì)象的調(diào)用,也可以通過其他如反射的方式對(duì)對(duì)象進(jìn)行調(diào)用(一般多耗時(shí)幾毫秒)在此不進(jìn)行詳細(xì)敘述愁茁。

publicclassFactory{

publicstaticDependencyBaseget(Nullable?dependencyBase){

if(dependencyBase?==null){

returnnewNullObject();

}

returnnewDependency();

}

}

這是一個(gè)使用范例蚕钦,通過這種模式,我們不再需要進(jìn)行對(duì)象的判空操作埋市,而是可以直接使用對(duì)象冠桃,也不必?fù)?dān)心NPE(NullPointerException)的問題。

publicclassClient{

publicvoidtest(DependencyBase?dependencyBase){

Factory.get(dependencyBase).Operation();

}

}

關(guān)于空對(duì)象模式道宅,更具體的內(nèi)容大家也可以多找一找資料食听,上述只是對(duì)NullObject的簡(jiǎn)單介紹,但是污茵,今天我要推薦的是一款協(xié)助判空的插件NR Null Object樱报,讓我們來優(yōu)雅地進(jìn)行判空,不再進(jìn)行一頓操作來定義繁瑣的空對(duì)象接口與空獨(dú)享實(shí)現(xiàn)類泞当。

.NR Null Object

NR Null Object是一款適用于Android Studio迹蛤、IntelliJ IDEA、PhpStorm襟士、WebStorm盗飒、PyCharm、RubyMine陋桂、AppCode逆趣、CLion、GoLand嗜历、DataGrip等IDEA的Intellij插件宣渗。其可以根據(jù)現(xiàn)有對(duì)象,便捷快速生成其空對(duì)象模式需要的組成成分梨州,其包含功能如下:

分析所選類可聲明為接口的方法痕囱;

抽象出公有接口;

創(chuàng)建空對(duì)象暴匠,自動(dòng)實(shí)現(xiàn)公有接口鞍恢;

對(duì)部分函數(shù)進(jìn)行可為空聲明;

可追加函數(shù)進(jìn)行再次生成每窖;

自動(dòng)的函數(shù)命名規(guī)范

讓我們來看一個(gè)使用范例:

怎么樣帮掉,看起來是不是非常快速便捷岛请,只需要在原有需要進(jìn)行多次判空的對(duì)象中旭寿,郵件彈出菜單,選擇Generate崇败,并選擇NR Null Object即可自動(dòng)生成相應(yīng)的空對(duì)象組件盅称。

那么如何來獲得這款插件呢?

安裝方式

可以直接通過IDEA的Preferences中的Plugins倉(cāng)庫(kù)進(jìn)行安裝后室。

選擇 Preferences → Plugins → Browse repositories

搜索“NR Null Oject”或者“Null Oject”進(jìn)行模糊查詢缩膝,點(diǎn)擊右側(cè)的Install,restart IDEA即可岸霹。

Optional

還有一種方式是使用Java8特性中的Optional來進(jìn)行優(yōu)雅地判空疾层,Optional來自官方的介紹如下:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

一個(gè)可能包含也可能不包含非null值的容器對(duì)象。如果存在值贡避,isPresent()將返回true痛黎,get()將返回該值予弧。

話不多說,舉個(gè)例子湖饱。

有如下代碼掖蛤,需要獲得Test2中的Info信息,但是參數(shù)為Test4井厌,我們要一層層的申請(qǐng)蚓庭,每一層都獲得的對(duì)象都可能是空,最后的代碼看起來就像這樣仅仆。

publicString?testSimple(Test4?test)?{

if(test?==null)?{

return"";

}

if(test.getTest3()?==null)?{

return"";

}

if(test.getTest3().getTest2()?==null)?{

return"";

}

if(test.getTest3().getTest2().getInfo()?==null)?{

return"";

}

returntest.getTest3().getTest2().getInfo();

}

但是使用Optional后器赞,整個(gè)就都不一樣了。

publicString?testOptional(Test?test)?{

returnOptional.ofNullable(test).flatMap(Test::getTest3)

.flatMap(Test3::getTest2)

.map(Test2::getInfo)

.orElse("");

}

1墓拜、Optional.ofNullable(test)港柜,如果test為空,則返回一個(gè)單例空Optional對(duì)象撮弧,如果非空則返回一個(gè)Optional包裝對(duì)象潘懊,Optional將test包裝;

publicstaticOptionalofNullable(Tvalue){

returnvalue==null??empty()?:?of(value);

}

2贿衍、flatMap(Test::getTest3)判斷test是否為空授舟,如果為空,繼續(xù)返回第一步中的單例Optional對(duì)象贸辈,否則調(diào)用Test的getTest3方法释树;

publicOptionalflatMap(Function<?super?T,?Optional>?mapper)?{

Objects.requireNonNull(mapper);

if(!isPresent())

returnempty();

else{

returnObjects.requireNonNull(mapper.apply(value));

}

}

3、flatMap(Test3::getTest2)同上調(diào)用Test3的getTest2方法擎淤;

4奢啥、map(Test2::getInfo)同flatMap類似,但是flatMap要求Test3::getTest2返回值為Optional類型嘴拢,而map不需要桩盲,flatMap不會(huì)多層包裝,map返回會(huì)再次包裝Optional席吴;

publicOptionalmap(Function<?super?T,???extends?U>?mapper)?{

Objects.requireNonNull(mapper);

if(!isPresent())

returnempty();

else{

returnOptional.ofNullable(mapper.apply(value));

}

}

5赌结、orElse("");獲得map中的value,不為空則直接返回value孝冒,為空則返回傳入的參數(shù)作為默認(rèn)值柬姚。

publicTorElse(T?other){

returnvalue!=null?value:?other;

}

怎么樣,使用Optional后我們的代碼是不是瞬間變得非常整潔庄涡,或許看到這段代碼你會(huì)有很多疑問量承,針對(duì)復(fù)雜的一長(zhǎng)串判空,Optional有它的優(yōu)勢(shì),但是對(duì)于簡(jiǎn)單的判空使用Optional也會(huì)增加代碼的閱讀成本撕捍、編碼量以及團(tuán)隊(duì)新成員的學(xué)習(xí)成本拿穴。畢竟Optional在現(xiàn)在還并沒有像RxJava那樣流行,它還擁有一定的局限性卦洽。

如果直接使用Java8中的Optional贞言,需要保證安卓API級(jí)別在24及以上斜棚。

你也可以直接引入Google的Guava阀蒂。(啥是Guava?來自官方的提示)

Guava is a set of core libraries that includes new collection types (such as multimap and multiset), immutable collections, a graph library, functional types, an in-memory cache, and APIs/utilities for concurrency, I/O, hashing, primitives, reflection, string processing, and much more!

引用方式弟蚀,就像這樣:

dependencies?{

compile'com.google.guava:guava:27.0-jre'

//or,forAndroid:

api'com.google.guava:guava:27.0-android'

}

過IDEA默認(rèn)會(huì)顯示黃色蚤霞,提示讓你將Guava表達(dá)式遷移到Java Api上。

當(dāng)然义钉,你也可以通過在Preferences搜索"Guava"來Kill掉這個(gè)Yellow的提示昧绣。

關(guān)于Optional使用還有很多技巧,感興趣可以查閱Guava和Java8相關(guān)書籍和文檔捶闸。

使用Optional具有如下優(yōu)點(diǎn):

將防御式編程代碼完美包裝

鏈?zhǔn)秸{(diào)用

有效避免程序代碼中的空指針

但是也同樣具有一些缺點(diǎn):

流行性不是非常理想夜畴,團(tuán)隊(duì)新成員需要學(xué)習(xí)成本

安卓中需要引入Guava,需要團(tuán)隊(duì)每個(gè)人處理IDEA默認(rèn)提示删壮,或者忍受黃色提示

時(shí)候代碼閱讀看起來可能會(huì)如下圖所示:

Kotlin

當(dāng)然贪绘,Kotlin以具有優(yōu)秀的空安全性為一大特色,并可以與Java很好的混合使用央碟,like this:

test1?.test2?.test3?.test4

如果你已經(jīng)開始使用了Kotlin税灌,可以不用再寫繚亂的防御判空語(yǔ)句。如果你還沒有使用Kotlin亿虽,并不推薦為了判空優(yōu)雅而直接轉(zhuǎn)向Kotlin菱涤。

擴(kuò)展閱讀

七點(diǎn)建議助您寫出優(yōu)雅的Java代碼

實(shí)戰(zhàn)經(jīng)驗(yàn)分析,如何優(yōu)雅地處理 Java 異常

SpringBoot (六) :如何優(yōu)雅的使用 mybatis

程序員面試過程中洛勉,該怎么判斷該公司好壞

如何在面試中介紹自己的項(xiàng)目經(jīng)驗(yàn)

【面試現(xiàn)場(chǎng)】如何在10億數(shù)中找出前1000大的數(shù)

來源:http://blog.imuxuan.com/archives/86

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粘秆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子收毫,更是在濱河造成了極大的恐慌攻走,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牛哺,死亡現(xiàn)場(chǎng)離奇詭異陋气,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)引润,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門巩趁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事议慰〈拦牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵别凹,是天一觀的道長(zhǎng)草讶。 經(jīng)常有香客問我,道長(zhǎng)炉菲,這世上最難降的妖魔是什么堕战? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拍霜,結(jié)果婚禮上嘱丢,老公的妹妹穿的比我還像新娘。我一直安慰自己祠饺,他們只是感情好越驻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著道偷,像睡著了一般缀旁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勺鸦,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天并巍,我揣著相機(jī)與錄音,去河邊找鬼祝旷。 笑死履澳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怀跛。 我是一名探鬼主播距贷,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吻谋!你這毒婦竟也來了忠蝗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤漓拾,失蹤者是張志新(化名)和其女友劉穎阁最,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骇两,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡速种,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了低千。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片配阵。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棋傍,到底是詐尸還是另有隱情救拉,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布瘫拣,位于F島的核電站亿絮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏麸拄。R本人自食惡果不足惜派昧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望感帅。 院中可真熱鬧斗锭,春花似錦、人聲如沸失球。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)实苞。三九已至,卻和暖如春烈疚,著一層夾襖步出監(jiān)牢的瞬間黔牵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工爷肝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猾浦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓灯抛,卻偏偏與公主長(zhǎng)得像金赦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子对嚼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容