NPE,讓我們太頭疼了髓考,該如何優(yōu)雅的處理null呢部念?

# 判空災(zāi)難

作為搬磚黨的一族們,我們對判空一定再熟悉不過了氨菇,不要跟我說你很少進行判空儡炼,除非你喜歡NullPointerException。

不過NullPointerException對于很多猿們來說查蓉,也是Exception家族中最親近的一員了乌询。

為了避免NullPointerException來找我們,我們經(jīng)常會進行如下操作豌研。

if(data!=null) {dosth.}

如果一個類中多次使用某個對象妹田,那你可能要一頓操作,so:

“世界第九大奇跡”就這樣誕生了鹃共。Maybe你會想鬼佣,項目中肯定不止你一個人會這樣一頓操作,然后按下Command+Shift+F霜浴,真相就在眼前:

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

好了,接下來晌纫,要進入正題了驻龟。

# NullObject模式

對于項目中無數(shù)次的判空,對代碼質(zhì)量整潔度產(chǎn)生了十分之惡劣的影響缸匪,對于這種現(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è)計模式語言 ”系列叢書中。一般的纺涤,在面向?qū)ο笳Z言中译暂,對對象的調(diào)用前需要使用判空檢查,來判斷這些對象是否為空撩炊,因為在空引用上無法調(diào)用所需方法外永。

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

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

Nullable是空對象的相關(guān)操作接口拧咳,用于確定對象是否為空伯顶,因為在空對象模式中,對象為空會被包裝成一個Object骆膝,成為Null Object祭衩,該對象會對原有對象的所有方法進行空實現(xiàn)…

publicinterfaceNullable{booleanisNull();}

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

publicinterfaceDependencyBaseextendsNullable{voidOperation();}

這是該對象的真實類阅签,實現(xiàn)了業(yè)務(wù)行為接口DependencyBase與空對象操作接口Nullable掐暮。

publicclassDependencyimplementsDependencyBase,Nullable{@OverridepublicvoidOperation(){System.out.print("Test!");? ? }@OverridepublicbooleanisNull(){returnfalse;? ? }}

這是空對象妻率,對原有對象的行為進行了空實現(xiàn)逊彭。

publicclassNullObjectimplementsDependencyBase{@OverridepublicvoidOperation(){// do nothing? ? }@OverridepublicbooleanisNull(){returntrue;? ? }}

在使用時避除,可以通過工廠調(diào)用方式來進行空對象的調(diào)用村斟,也可以通過其他如反射的方式對對象進行調(diào)用(一般多耗時幾毫秒)在此不進行詳細敘述棠绘。

publicclassFactory{publicstaticDependencyBaseget(Nullable dependencyBase){if(dependencyBase ==null){returnnewNullObject();? ? ? ? }returnnewDependency();? ? }}

這是一個使用范例饵蒂,通過這種模式侨拦,我們不再需要進行對象的判空操作稠通,而是可以直接使用對象层坠,也不必擔(dān)心NPE(NullPointerException)的問題殖妇。

publicclassClient{publicvoidtest(DependencyBase dependencyBase){Factory.get(dependencyBase).Operation();? ? }}

關(guān)于空對象模式,更具體的內(nèi)容大家也可以多找一找資料破花,上述只是對NullObject的簡單介紹谦趣,但是疲吸,今天我要推薦的是一款協(xié)助判空的插件NR Null Object,讓我們來優(yōu)雅地進行判空前鹅,不再進行一頓操作來定義繁瑣的空對象接口與空獨享實現(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)有對象驳概,便捷快速生成其空對象模式需要的組成成分赤嚼,其包含功能如下:

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

抽象出公有接口抡句;

創(chuàng)建空對象探膊,自動實現(xiàn)公有接口;

對部分函數(shù)進行可為空聲明待榔;

可追加函數(shù)進行再次生成逞壁;

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

讓我們來看一個使用范例:

怎么樣,看起來是不是非橙衤啵快速便捷腌闯,只需要在原有需要進行多次判空的對象中,郵件彈出菜單雕憔,選擇Generate姿骏,并選擇NR Null Object即可自動生成相應(yīng)的空對象組件。

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

安裝方式

可以直接通過IDEA的Preferences中的Plugins倉庫進行安裝分瘦。

選擇 Preferences → Plugins → Browse repositories

搜索“NR Null Oject”或者“Null Oject”進行模糊查詢,點擊右側(cè)的Install琉苇,restart IDEA即可嘲玫。

Optional

還有一種方式是使用Java8特性中的Optional來進行優(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.

一個可能包含也可能不包含非null值的容器對象并扇。如果存在值去团,isPresent()將返回true,get()將返回該值。

話不多說土陪,舉個例子昼汗。

有如下代碼,需要獲得Test2中的Info信息鬼雀,但是參數(shù)為Test4顷窒,我們要一層層的申請,每一層都獲得的對象都可能是空源哩,最后的代碼看起來就像這樣蹋肮。

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后,整個就都不一樣了璧疗。

publicString testOptional(Test test) {returnOptional.ofNullable(test).flatMap(Test::getTest3)? ? ? ? ? ? ? ? .flatMap(Test3::getTest2)? ? ? ? ? ? ? ? .map(Test2::getInfo).orElse("");? ? }

1、Optional.ofNullable(test)馁龟,如果test為空崩侠,則返回一個單例空Optional對象,如果非空則返回一個Optional包裝對象坷檩,Optional將test包裝却音;


publicstaticOptionalofNullable(Tvalue){returnvalue==null? empty() : of(value);? ? }

2、flatMap(Test::getTest3)判斷test是否為空矢炼,如果為空系瓢,繼續(xù)返回第一步中的單例Optional對象,否則調(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不會多層包裝酬土,map返回會再次包裝Optional;

public Optional map(Function mapper) {? ? ? ? Objects.requireNonNull(mapper);if(!isPresent())returnempty();else{returnOptional.ofNullable(mapper.apply(value));? ? ? ? }? ? }

5格带、orElse("");獲得map中的value撤缴,不為空則直接返回value,為空則返回傳入的參數(shù)作為默認值叽唱。

publicTorElse(T other){returnvalue!=null?value: other;}

怎么樣屈呕,使用Optional后我們的代碼是不是瞬間變得非常整潔,或許看到這段代碼你會有很多疑問尔觉,針對復(fù)雜的一長串判空凉袱,Optional有它的優(yōu)勢,但是對于簡單的判空使用Optional也會增加代碼的閱讀成本、編碼量以及團隊新成員的學(xué)習(xí)成本专甩。畢竟Optional在現(xiàn)在還并沒有像RxJava那樣流行钟鸵,它還擁有一定的局限性。

如果直接使用Java8中的Optional涤躲,需要保證安卓API級別在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, for Android:api'com.google.guava:guava:27.0-android'? ? }

不過IDEA默認會顯示黃色,提示讓你將Guava表達式遷移到Java Api上嫩挤。

當然害幅,你也可以通過在Preferences搜索"Guava"來Kill掉這個Yellow的提示。

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

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

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

鏈式調(diào)用

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

但是也同樣具有一些缺點:

流行性不是非常理想,團隊新成員需要學(xué)習(xí)成本

安卓中需要引入Guava约啊,需要團隊每個人處理IDEA默認提示邑遏,或者忍受黃色提示

有時候代碼閱讀看起來可能會如下圖所示:

Kotlin

當然,Kotlin以具有優(yōu)秀的空安全性為一大特色恰矩,并可以與Java很好的混合使用记盒,like this:

test1?.test2?.test3?.test4

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萎胰,一起剝皮案震驚了整個濱河市彬碱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奥洼,老刑警劉巖巷疼,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灵奖,居然都是意外死亡嚼沿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門瓷患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骡尽,“玉大人,你說我怎么就攤上這事擅编∨氏福” “怎么了箫踩?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谭贪。 經(jīng)常有香客問我境钟,道長,這世上最難降的妖魔是什么俭识? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任慨削,我火速辦了婚禮,結(jié)果婚禮上套媚,老公的妹妹穿的比我還像新娘缚态。我一直安慰自己,他們只是感情好堤瘤,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布玫芦。 她就那樣靜靜地躺著,像睡著了一般本辐。 火紅的嫁衣襯著肌膚如雪姨俩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天师郑,我揣著相機與錄音,去河邊找鬼调窍。 笑死宝冕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的邓萨。 我是一名探鬼主播地梨,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缔恳!你這毒婦竟也來了宝剖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤歉甚,失蹤者是張志新(化名)和其女友劉穎万细,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纸泄,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡赖钞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聘裁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雪营。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衡便,靈堂內(nèi)的尸體忽然破棺而出献起,到底是詐尸還是另有隱情洋访,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布谴餐,位于F島的核電站姻政,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏总寒。R本人自食惡果不足惜扶歪,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摄闸。 院中可真熱鬧善镰,春花似錦、人聲如沸年枕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熏兄。三九已至品洛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摩桶,已是汗流浹背桥状。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留硝清,地道東北人辅斟。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像芦拿,于是被迫代替她去往敵國和親士飒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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