小編給大家分享一下利用Java8 Optional避免空指針異常的示例,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
創(chuàng)新互聯(lián)成都網(wǎng)站建設(shè)按需求定制網(wǎng)站,是成都網(wǎng)站營(yíng)銷推廣公司,為崗?fù)?/a>提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計(jì)服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計(jì)、前端HTML5制作、后臺(tái)程序開(kāi)發(fā)等。成都網(wǎng)站營(yíng)銷推廣熱線:18982081108
前言
空指針是我們最常見(jiàn)也最討厭的異常,為了防止空指針異常,你不得在代碼里寫(xiě)大量的非空判斷。
Java 8引入了一個(gè)新的Optional類。用于避免空指針的出現(xiàn),也無(wú)需在寫(xiě)大量的if(obj!=null)
這樣的判斷了,前提是你得將數(shù)據(jù)用Optional裝著,它就是一個(gè)包裹著對(duì)象的容器。
都說(shuō)沒(méi)有遇到過(guò)空指針異常的程序員不是Java程序員,null確實(shí)引發(fā)過(guò)很多問(wèn)題。Java 8中引入了一個(gè)叫做java.util.Optional
的新類可以避免null引起的諸多問(wèn)題。
我們看看一個(gè)null引用能導(dǎo)致哪些危害。首先創(chuàng)建一個(gè)類Computer,結(jié)構(gòu)如下圖所示:
當(dāng)我們調(diào)用如下代碼會(huì)怎樣?
String version = computer.getSoundcard().getUSB().getVersion();
上述代碼看似是沒(méi)有問(wèn)題的,但是很多計(jì)算機(jī)(比如,樹(shù)莓派)其實(shí)是沒(méi)有聲卡的,那么調(diào)用getSoundcard()
方法可定會(huì)拋出空指針異常了。
一個(gè)常規(guī)的但是不好的的方法是返回一個(gè)null引用來(lái)表示計(jì)算機(jī)沒(méi)有聲卡,但是這就意味著會(huì)對(duì)一個(gè)空引調(diào)用getUSB()方法,顯然會(huì)在程序運(yùn)行過(guò)程中拋出控制異常,從而導(dǎo)致程序停止運(yùn)行。想想一下,當(dāng)你的程序在客戶端電腦上運(yùn)行時(shí),突然出現(xiàn)這種錯(cuò)是多尷尬的一件事?
偉大計(jì)算機(jī)科學(xué)Tony Hoare曾經(jīng)寫(xiě)到:"我認(rèn)為null引用從1965年被創(chuàng)造出來(lái)導(dǎo)致了十億美元的損失。當(dāng)初使用null引用對(duì)我最大的誘惑就是它實(shí)現(xiàn)起來(lái)方便。"
那么該怎么避免在程序運(yùn)行時(shí)會(huì)出現(xiàn)空指針異常呢?你需要保持警惕,并且不斷檢查可能出現(xiàn)空指針的情況,就像下面這樣:
String version = "UNKNOWN"; if(computer != null) { Soundcard soundcard = computer.getSoundcard(); if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null){ version = usb.getVersion(); } } }
然而,你可以看到上述代碼有太多的null檢查,整個(gè)代碼結(jié)構(gòu)變得非常丑陋。但是我們又不得不通過(guò)這樣的判斷來(lái)確保系統(tǒng)運(yùn)行時(shí)不會(huì)出現(xiàn)空指針。如果在我們的業(yè)務(wù)代碼中出現(xiàn)大量的這種空引用判斷簡(jiǎn)直讓人惱火,也導(dǎo)致我們代碼的可讀性會(huì)很差。
如果你忘記檢查要給值是否為空,null引用也是存在很大的潛在問(wèn)題。這篇文章我將證明使用null引用作為值不存在的表示是不好的方法。我們需要一個(gè)更好的表示值不存在的模型,而不是再使用null引用。
Java 8引入了一個(gè)新類叫做java.util.Optional<T>
,這個(gè)類的設(shè)計(jì)的靈感來(lái)源于Haskell語(yǔ)言和Scala語(yǔ)言。這個(gè)類可以包含了一個(gè)任意值,像下面圖和代碼表示的那樣。你可以把Optional看做是一個(gè)有可能包含了值的值,如果Optional不包含值那么它就是空的,下圖那樣。
public class Computer { private Optional<Soundcard> soundcard; public Optional<Soundcard> getSoundcard() { ... } ... } public class Soundcard { private Optional<USB> usb; public Optional<USB> getUSB() { ... } } public class USB{ public String getVersion(){ ... } }
上述代碼展現(xiàn)了一臺(tái)計(jì)算機(jī)有可能包換一個(gè)聲卡(聲卡是有可能存在也有可能不存在)。聲卡也是有可能包含一個(gè)USB端口的。這是一種改善方法,該模型可以更加清晰的反映一個(gè)被給定的值是可以不存在的。
但是該怎么處理Optional<Soundcard>
這個(gè)對(duì)象呢?畢竟,你想要獲取的是USB的端口號(hào)。很簡(jiǎn)單,Optional類包含了一些方法來(lái)處理值是否存在的狀況。和null引用相比Optional類迫使你在你要做值是否相關(guān)處理,從而避免了空指針異常。
需要說(shuō)明的是Optional類并不是要取代null引用。相反地,是為了讓設(shè)計(jì)的API更容易被理解,當(dāng)你看到一個(gè)函數(shù)的簽名時(shí),你就可以判斷要傳遞給這個(gè)函數(shù)的值是不是有可能不存在。這就促使你要打開(kāi)Optional類來(lái)處理確實(shí)值的狀況了。
采用Optional模式
啰嗦了這么多,來(lái)看一些代碼吧!我們先看一下怎么使用Optional改寫(xiě)傳統(tǒng)的null引用檢測(cè)后是什么樣子。在這邊文章的末尾你將會(huì)明白怎么使用Optional。
String name = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");
創(chuàng)建Optional對(duì)象
可以創(chuàng)建一個(gè)空的Optional對(duì)象:
Optional<Soundcard> sc = Optional.empty();
接下來(lái)是創(chuàng)建一個(gè)包含非null值的Optional:
SoundCard soundcard = new Soundcard(); Optional<Soundcard> sc = Optional.of(soundcard);
如果聲卡null,空指針異常會(huì)立即被拋出(這比在獲取聲卡屬性時(shí)才拋出要好)。
通過(guò)使用ofNullable,你可以創(chuàng)建一個(gè)可能包含null引用的Optional對(duì)象:
Optional<Soundcard> sc = Optional.ofNullable(soundcard);
如果聲卡是null 引用,Optional對(duì)象就是一個(gè)空的。
對(duì)Optional中的值的處理
既然現(xiàn)在已經(jīng)有了Optional對(duì)象,你可以調(diào)用相應(yīng)的方法來(lái)處理Optional對(duì)象中的值是否存在。和進(jìn)行null檢測(cè)相比,我們可以使用ifPresent()方法,像下面這樣:
Optional<Soundcard> soundcard = ...; soundcard.ifPresent(System.out::println);
這樣就不必再做null檢測(cè),如果Optional對(duì)象是空的,那么什么信息將不會(huì)打印出來(lái)。
你也可以使用isPresent()
方法查看Optional對(duì)象是否真的存在。另外,還有一個(gè)get()方法可以返回Optional對(duì)象中的包含的值,如果存在的話。否則會(huì)拋出一個(gè)NoSuchElementException異常。這兩個(gè)方式可以像下面這樣搭配起來(lái)使用,從而避免異常:
if(soundcard.isPresent()){ System.out.println(soundcard.get()); }
但是這種方式不推薦使用(它和null檢測(cè)相比沒(méi)有什么改進(jìn)),下面我們將會(huì)探討一下工作慣用的方式。
返回默認(rèn)值和相關(guān)操作
當(dāng)遇到null時(shí)一個(gè)常規(guī)的操作就是返回一個(gè)默認(rèn)值,你可以使用三元表達(dá)式來(lái)實(shí)現(xiàn):
Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");
使用Optional對(duì)象的話,你可以orElse()
使用重寫(xiě),當(dāng)Optional是空的時(shí)候orElse()
可以返回一個(gè)默認(rèn)值:
Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut"));
類似地,當(dāng)Optional為空的時(shí)候也可以使用orElseThrow()拋出異常:
Soundcard soundcard = maybeSoundCard.orElseThrow(IllegalStateException::new);
使用filter過(guò)濾特定的值
我們常常會(huì)調(diào)用一個(gè)對(duì)象的方法來(lái)判斷它的一下屬性。比如,你可能需要檢測(cè)USB端口號(hào)是否是某個(gè)特定值。為了安全起見(jiàn),你需要檢查指向USB的醫(yī)用是否是null,然后再調(diào)用getVersion()
方法,像下面這樣:
USB usb = ...; if(usb != null && "3.0".equals(usb.getVersion())){ System.out.println("ok"); }
如果使用Optional的話可以使用filter函數(shù)重寫(xiě):
Optional<USB> maybeUSB = ...; maybeUSB.filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok"));
filter方法需要一個(gè)predicate對(duì)向作為參數(shù)。如果Optional中的值存在并且滿足predicate,那么filter函數(shù)將會(huì)返回滿足條件的值;否則,會(huì)返回一個(gè)空的Optional對(duì)象。
使用map方法進(jìn)行數(shù)據(jù)的提取和轉(zhuǎn)化
一個(gè)常見(jiàn)的模式是提取一個(gè)對(duì)象的一些屬性。比如,對(duì)于一個(gè)Soundcard對(duì)象,你可能需要獲取它的USB對(duì)象,然后判斷它的的版本號(hào)。通常我們的實(shí)現(xiàn)方式是這樣的:
if(soundcard != null){ USB usb = soundcard.getUSB(); if(usb != null && "3.0".equals(usb.getVersion()){ System.out.println("ok"); } }
我們可以使用map方法重寫(xiě)這種檢測(cè)null,然后再提取對(duì)象類型的對(duì)象。
Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);
這個(gè)和使用stream的map函數(shù)式一樣的。使用stream需要給map函數(shù)傳遞一個(gè)函數(shù)作為參數(shù),這個(gè)傳遞進(jìn)來(lái)的函數(shù)將會(huì)應(yīng)用于stream中的每個(gè)元素。當(dāng)stream時(shí)空的時(shí)候,什么也不會(huì)發(fā)生。
Optional中包含的值將會(huì)被傳遞進(jìn)來(lái)的函數(shù)轉(zhuǎn)化(這里是一個(gè)從聲卡中獲取USB的函數(shù))。如果Optional對(duì)象時(shí)空的,那么什么也不會(huì)發(fā)生。
然后,我們結(jié)合map方法和filter方法過(guò)濾掉USB的版本號(hào)不是3.0的聲卡。
maybeSoundcard.map(Soundcard::getUSB) .filter(usb -> "3.0".equals(usb.getVersion()) .ifPresent(() -> System.out.println("ok"));
這樣我們的代碼開(kāi)始變得像有點(diǎn)像開(kāi)始我們給出的樣子,沒(méi)有了null檢測(cè)。
使用flatMap函數(shù)傳遞Optional對(duì)象
現(xiàn)在已經(jīng)介紹了一個(gè)可以使用Optional重構(gòu)代碼的例子,那么我們應(yīng)該如何使用安全的方式實(shí)現(xiàn)下面代碼呢?
String version = computer.getSoundcard().getUSB().getVersion();
注意上面的代碼都是從一個(gè)對(duì)象中提取另一個(gè)對(duì)象,使用map函數(shù)可以實(shí)現(xiàn)。在前面的文章中我們?cè)O(shè)置了Computer中包含的是一個(gè)Optional<Soundcard>
對(duì)象,Soundcard包含的是一個(gè)Optional<USB>
對(duì)象,因此我們可以這么重構(gòu)代碼
String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");
不幸的是,上面的代碼會(huì)編譯錯(cuò)誤,那么為什么呢?computer變量是Optional<Computer>
類型的,所以它調(diào)用map函數(shù)是沒(méi)有問(wèn)題的。但是getSoundcard()
方法返回的是一個(gè)Optional<Soundcard>
的對(duì)象,返回的是Optional<Optional<Soundcard>>
類型的對(duì)象,進(jìn)行了第二次map函數(shù)的調(diào)用,結(jié)果調(diào)用getUSB()
函數(shù)就變成非法的了。
下面的圖描述了這種場(chǎng)景:
map函數(shù)的源碼實(shí)現(xiàn)是這樣的:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); } }
可以看出map函數(shù)還會(huì)再調(diào)用一次Optional.ofNullable()
, 從而導(dǎo)致返回Optional<Optional<Soundcard>>
Optional提供了flatMap這個(gè)函數(shù),它的設(shè)計(jì)意圖是當(dāng)對(duì)Optional對(duì)象的值進(jìn)行轉(zhuǎn)化(就像map操作)然后一個(gè)兩級(jí)Optional壓縮成一個(gè)。下面的圖展示了Optional對(duì)象通過(guò)調(diào)用map和flatMap進(jìn)行類型轉(zhuǎn)化的不同:
因此我們可以這樣寫(xiě):
String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN");
第一個(gè)flatMap保證了返回的是Optional<Soundcard>
而不是Optional<Optional<Soundcard>>
,第二個(gè)flatMap實(shí)現(xiàn)了同樣的功能從而返回的是 Optional<USB>
。注意第三次調(diào)用了map()
,因?yàn)?code>getVersion()返回的是一個(gè)String對(duì)象而不是一個(gè)Optional對(duì)象。
我們終于把剛開(kāi)始使用的嵌套null檢查的丑陋代碼改寫(xiě)了可讀性高的代碼,也避免了空指針異常的出現(xiàn)的代碼。
看完了這篇文章,相信你對(duì)“利用Java8 Optional避免空指針異常的示例”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
本文題目:利用Java8Optional避免空指針異常的示例
文章鏈接:http://aaarwkj.com/article38/goocpp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站制作、動(dòng)態(tài)網(wǎng)站、網(wǎng)站設(shè)計(jì)公司、App開(kāi)發(fā)、商城網(wǎng)站、
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)