最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用Java8 Optional如何避免空指針異常詳解

 更新時(shí)間:2018年01月12日 10:32:23   作者:劉亞濤  
Optional可以讓你的代碼具有可讀性,且會(huì)避免出現(xiàn)空指針異常。 下面這篇文章主要給大家介紹了關(guān)于利用Java8 Optional如何避免空指針異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。

前言

空指針是我們最常見也最討厭的異常,為了防止空指針異常,你不得在代碼里寫大量的非空判斷。

Java 8引入了一個(gè)新的Optional類。用于避免空指針的出現(xiàn),也無需在寫大量的if(obj!=null)這樣的判斷了,前提是你得將數(shù)據(jù)用Optional裝著,它就是一個(gè)包裹著對(duì)象的容器。

都說沒有遇到過空指針異常的程序員不是Java程序員,null確實(shí)引發(fā)過很多問題。Java 8中引入了一個(gè)叫做java.util.Optional的新類可以避免null引起的諸多問題。

我們看看一個(gè)null引用能導(dǎo)致哪些危害。首先創(chuàng)建一個(gè)類Computer,結(jié)構(gòu)如下圖所示:

當(dāng)我們調(diào)用如下代碼會(huì)怎樣?

String version = computer.getSoundcard().getUSB().getVersion();

上述代碼看似是沒有問題的,但是很多計(jì)算機(jī)(比如,樹莓派)其實(shí)是沒有聲卡的,那么調(diào)用getSoundcard()方法可定會(huì)拋出空指針異常了。

一個(gè)常規(guī)的但是不好的的方法是返回一個(gè)null引用來表示計(jì)算機(jī)沒有聲卡,但是這就意味著會(huì)對(duì)一個(gè)空引調(diào)用getUSB()方法,顯然會(huì)在程序運(yùn)行過程中拋出控制異常,從而導(dǎo)致程序停止運(yùn)行。想想一下,當(dāng)你的程序在客戶端電腦上運(yùn)行時(shí),突然出現(xiàn)這種錯(cuò)是多尷尬的一件事?

偉大計(jì)算機(jī)科學(xué)Tony Hoare曾經(jīng)寫到:"我認(rèn)為null引用從1965年被創(chuàng)造出來導(dǎo)致了十億美元的損失。當(dāng)初使用null引用對(duì)我最大的誘惑就是它實(shí)現(xiàn)起來方便。"

那么該怎么避免在程序運(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)變得非常丑陋。但是我們又不得不通過這樣的判斷來確保系統(tǒng)運(yùn)行時(shí)不會(huì)出現(xiàn)空指針。如果在我們的業(yè)務(wù)代碼中出現(xiàn)大量的這種空引用判斷簡直讓人惱火,也導(dǎo)致我們代碼的可讀性會(huì)很差。

如果你忘記檢查要給值是否為空,null引用也是存在很大的潛在問題。這篇文章我將證明使用null引用作為值不存在的表示是不好的方法。我們需要一個(gè)更好的表示值不存在的模型,而不是再使用null引用。

Java 8引入了一個(gè)新類叫做java.util.Optional<T> ,這個(gè)類的設(shè)計(jì)的靈感來源于Haskell語言和Scala語言。這個(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)。很簡單,Optional類包含了一些方法來處理值是否存在的狀況。和null引用相比Optional類迫使你在你要做值是否相關(guān)處理,從而避免了空指針異常。

需要說明的是Optional類并不是要取代null引用。相反地,是為了讓設(shè)計(jì)的API更容易被理解,當(dāng)你看到一個(gè)函數(shù)的簽名時(shí),你就可以判斷要傳遞給這個(gè)函數(shù)的值是不是有可能不存在。這就促使你要打開Optional類來處理確實(shí)值的狀況了。

采用Optional模式

啰嗦了這么多,來看一些代碼吧!我們先看一下怎么使用Optional改寫傳統(tǒng)的null引用檢測后是什么樣子。在這邊文章的末尾你將會(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();

接下來是創(chuàng)建一個(gè)包含非null值的Optional:

SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard);

如果聲卡null,空指針異常會(huì)立即被拋出(這比在獲取聲卡屬性時(shí)才拋出要好)。

通過使用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)的方法來處理Optional對(duì)象中的值是否存在。和進(jìn)行null檢測相比,我們可以使用ifPresent()方法,像下面這樣:

Optional<Soundcard> soundcard = ...;
soundcard.ifPresent(System.out::println);

這樣就不必再做null檢測,如果Optional對(duì)象是空的,那么什么信息將不會(huì)打印出來。

你也可以使用isPresent()方法查看Optional對(duì)象是否真的存在。另外,還有一個(gè)get()方法可以返回Optional對(duì)象中的包含的值,如果存在的話。否則會(huì)拋出一個(gè)NoSuchElementException異常。這兩個(gè)方式可以像下面這樣搭配起來使用,從而避免異常:

if(soundcard.isPresent()){
 System.out.println(soundcard.get());
}

但是這種方式不推薦使用(它和null檢測相比沒有什么改進(jìn)),下面我們將會(huì)探討一下工作慣用的方式。

返回默認(rèn)值和相關(guān)操作

當(dāng)遇到null時(shí)一個(gè)常規(guī)的操作就是返回一個(gè)默認(rèn)值,你可以使用三元表達(dá)式來實(shí)現(xiàn):

Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");

使用Optional對(duì)象的話,你可以orElse()使用重寫,當(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過濾特定的值

我們常常會(huì)調(diào)用一個(gè)對(duì)象的方法來判斷它的一下屬性。比如,你可能需要檢測USB端口號(hào)是否是某個(gè)特定值。為了安全起見,你需要檢查指向USB的醫(yī)用是否是null,然后再調(diào)用getVersion()方法,像下面這樣:

USB usb = ...;
if(usb != null && "3.0".equals(usb.getVersion())){
 System.out.println("ok");
}

如果使用Optional的話可以使用filter函數(shù)重寫:

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è)常見的模式是提取一個(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方法重寫這種檢測null,然后再提取對(duì)象類型的對(duì)象。

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);

這個(gè)和使用stream的map函數(shù)式一樣的。使用stream需要給map函數(shù)傳遞一個(gè)函數(shù)作為參數(shù),這個(gè)傳遞進(jìn)來的函數(shù)將會(huì)應(yīng)用于stream中的每個(gè)元素。當(dāng)stream時(shí)空的時(shí)候,什么也不會(huì)發(fā)生。

Optional中包含的值將會(huì)被傳遞進(jìn)來的函數(shù)轉(zhuǎn)化(這里是一個(gè)從聲卡中獲取USB的函數(shù))。如果Optional對(duì)象時(shí)空的,那么什么也不會(huì)發(fā)生。

然后,我們結(jié)合map方法和filter方法過濾掉USB的版本號(hào)不是3.0的聲卡。

maybeSoundcard.map(Soundcard::getUSB)
  .filter(usb -> "3.0".equals(usb.getVersion())
  .ifPresent(() -> System.out.println("ok"));

這樣我們的代碼開始變得像有點(diǎn)像開始我們給出的樣子,沒有了null檢測。

使用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ù)是沒有問題的。但是getSoundcard()方法返回的是一個(gè)Optional<Soundcard>的對(duì)象,返回的是Optional<Optional<Soundcard>>類型的對(duì)象,進(jìn)行了第二次map函數(shù)的調(diào)用,結(jié)果調(diào)用getUSB()函數(shù)就變成非法的了。

下面的圖描述了這種場景:


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ì)象通過調(diào)用map和flatMap進(jìn)行類型轉(zhuǎn)化的不同:


因此我們可以這樣寫:

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ì)象。

我們終于把剛開始使用的嵌套null檢查的丑陋代碼改寫了可讀性高的代碼,也避免了空指針異常的出現(xiàn)的代碼。

總結(jié)

在這片文章中我們采用了Java 8提供的新類java.util.Optional<T> 。這個(gè)類的初衷不是要取代null引用,而是幫助設(shè)計(jì)者設(shè)計(jì)出更好的API,只要讀到函數(shù)的簽名就可知道該函數(shù)是否接受一個(gè)可能存在也可能不存在的值。另外,Optional迫使你去打開Optional,然后處理值是否存在,這就使得你的代碼避免了潛在的空指針異常。

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Java實(shí)現(xiàn)爬取往期所有雙色球開獎(jiǎng)結(jié)果功能示例

    Java實(shí)現(xiàn)爬取往期所有雙色球開獎(jiǎng)結(jié)果功能示例

    這篇文章主要介紹了Java實(shí)現(xiàn)爬取往期所有雙色球開獎(jiǎng)結(jié)果功能,涉及Java網(wǎng)頁抓取、正則替換、文件讀寫等相關(guān)操作技巧,需要的朋友可以參考下
    2018-07-07
  • 詳細(xì)介紹Java阿里云的短信驗(yàn)證碼實(shí)現(xiàn)

    詳細(xì)介紹Java阿里云的短信驗(yàn)證碼實(shí)現(xiàn)

    這篇文章主要介紹了詳細(xì)介紹Java阿里云的短信驗(yàn)證碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • MyBatis一次執(zhí)行多條SQL語句的操作

    MyBatis一次執(zhí)行多條SQL語句的操作

    這篇文章主要介紹了MyBatis一次執(zhí)行多條SQL語句的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Java?binLog日志監(jiān)聽方式

    Java?binLog日志監(jiān)聽方式

    文章介紹了如何在Windows下開啟MySQL的binLog日志,并提供了一個(gè)Java代碼示例,演示如何監(jiān)聽指定的表并進(jìn)行處理邏輯
    2024-11-11
  • Java 實(shí)現(xiàn)瀏覽器下載文件及文件預(yù)覽

    Java 實(shí)現(xiàn)瀏覽器下載文件及文件預(yù)覽

    這篇文章主要介紹了Java 實(shí)現(xiàn)瀏覽器下載文件及文件預(yù)覽,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringCache源碼解析Annotation案例講解

    SpringCache源碼解析Annotation案例講解

    這篇文章主要介紹了SpringCache源碼解析Annotation的相關(guān)知識(shí),本文通過案例講解的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 深入解析Java中volatile關(guān)鍵字的作用

    深入解析Java中volatile關(guān)鍵字的作用

    Java語言是支持多線程的,為了解決線程并發(fā)的問題,在語言內(nèi)部引入了 同步塊 和 volatile 關(guān)鍵字機(jī)制
    2013-09-09
  • Spring?boot?自定義?Starter及自動(dòng)配置的方法

    Spring?boot?自定義?Starter及自動(dòng)配置的方法

    Starter?組件是?Spring?boot?的一個(gè)核心特性,Starter組件的出現(xiàn)極大的簡化了項(xiàng)目開發(fā),這篇文章主要介紹了Spring?boot?自定義?Starter?及?自動(dòng)配置,需要的朋友可以參考下
    2022-12-12
  • java實(shí)現(xiàn)文件的上傳功能

    java實(shí)現(xiàn)文件的上傳功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)文件的上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Tomcat 多端口 多應(yīng)用

    Tomcat 多端口 多應(yīng)用

    當(dāng)你使用Tomcat作為Web Server的時(shí)候,是不是會(huì)想過這樣的一個(gè)問題:如何利用Tomcat建立兩個(gè)Web Server,同時(shí)偵聽來自不同端口的請(qǐng)求呢?要實(shí)現(xiàn)這一點(diǎn)是很簡單的。以下假設(shè)Tomcat安裝在D:/Tomcat5.0目錄下。
    2008-02-02

最新評(píng)論

石河子市| 伊宁市| 岳阳县| 宝应县| 石阡县| 肃宁县| 沙坪坝区| 革吉县| 洪洞县| 鄂托克前旗| 凤山县| SHOW| 高雄市| 宿迁市| 深泽县| 民乐县| 保定市| 拉萨市| 施甸县| 鲁山县| 西平县| 葫芦岛市| 内丘县| 东方市| 阳山县| 南丹县| 清水河县| 塔城市| 沭阳县| 马山县| 武清区| 建阳市| 韩城市| 闸北区| 神农架林区| 福贡县| 乌拉特中旗| 永宁县| 通山县| 图片| 福建省|