亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

利用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)大量的這種空引用判斷簡(jiǎ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)。很簡(jiǎn)單,Optional類包含了一些方法來處理值是否存在的狀況。和null引用相比Optional類迫使你在你要做值是否相關(guān)處理,從而避免了空指針異常。

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

采用Optional模式

啰嗦了這么多,來看一些代碼吧!我們先看一下怎么使用Optional改寫傳統(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();

接下來是創(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檢測(cè)相比,我們可以使用ifPresent()方法,像下面這樣:

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

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

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

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

但是這種方式不推薦使用(它和null檢測(cè)相比沒有什么改進(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ì)象的方法來判斷它的一下屬性。比如,你可能需要檢測(cè)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方法重寫這種檢測(cè)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檢測(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ù)是沒有問題的。但是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ì)象通過調(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,然后處理值是否存在,這就使得你的代碼避免了潛在的空指針異常。

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

相關(guān)文章

  • 世界著名程序SpringMVC完整過程

    世界著名程序SpringMVC完整過程

    這篇文章主要為大家介紹了世界著名程序SpringMVC實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 分隔List集合,按指定大小,將集合分成多個(gè)的方法

    分隔List集合,按指定大小,將集合分成多個(gè)的方法

    下面小編就為大家?guī)硪黄指鬖ist集合,按指定大小,將集合分成多個(gè)的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-03-03
  • Spring的BeanUtils.copyProperties屬性復(fù)制避坑指南

    Spring的BeanUtils.copyProperties屬性復(fù)制避坑指南

    這篇文章主要介紹了Spring的BeanUtils.copyProperties屬性復(fù)制避坑指南,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • 詳解IDEA中SpringBoot整合Servlet三大組件的過程

    詳解IDEA中SpringBoot整合Servlet三大組件的過程

    這篇文章主要介紹了詳解IDEA中SpringBoot整合Servlet三大組件的過程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Java中使用fileupload組件實(shí)現(xiàn)文件上傳功能的實(shí)例代碼

    Java中使用fileupload組件實(shí)現(xiàn)文件上傳功能的實(shí)例代碼

    這篇文章主要介紹了Java中使用fileupload組件實(shí)現(xiàn)文件上傳功能的實(shí)例代碼,需要的朋友可以參考下
    2017-05-05
  • SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問題(推薦)

    SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問題(推薦)

    這篇文章主要介紹了SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問題,定時(shí)任務(wù)多機(jī)器部署解決方案,方式一拆分,單獨(dú)拆分出來,單獨(dú)跑一個(gè)應(yīng)用,方式二是基于aop攔截處理(搶占執(zhí)行),只要有一個(gè)執(zhí)行,其它都不執(zhí)行,需要的朋友可以參考下
    2023-01-01
  • SpringBoot實(shí)現(xiàn)郵件任務(wù)的步驟詳解

    SpringBoot實(shí)現(xiàn)郵件任務(wù)的步驟詳解

    這篇文章主要介紹了SpringBoot實(shí)現(xiàn)郵件任務(wù)的步驟詳解,使用Spring Boot實(shí)現(xiàn)QQ郵箱發(fā)送郵件具有快速集成、統(tǒng)一的開發(fā)體驗(yàn)、強(qiáng)大的維護(hù)和擴(kuò)展能力、可靠的送達(dá)性和安全性等優(yōu)勢(shì),可以幫助你快速構(gòu)建穩(wěn)定可靠的郵件發(fā)送功能,需要的朋友可以參考下
    2023-10-10
  • Spring核心IoC容器的依賴注入接口和層級(jí)包命名規(guī)范

    Spring核心IoC容器的依賴注入接口和層級(jí)包命名規(guī)范

    這篇文章主要介紹了Spring核心IoC容器的依賴注入接口和層級(jí)包命名規(guī)范,IOC又名控制反轉(zhuǎn),把對(duì)象創(chuàng)建和對(duì)象之間的調(diào)用過程,交給Spring進(jìn)行管理,目的是為了降低耦合度,需要的朋友可以參考下
    2023-05-05
  • 基于@RequestMapping 用法詳解之地址映射

    基于@RequestMapping 用法詳解之地址映射

    這篇文章主要介紹了@RequestMapping 用法詳解之地址映射,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java面試常見模式問題---單例模式

    java面試常見模式問題---單例模式

    單例模式(Singleton Pattern)是 Java 中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式
    2021-06-06

最新評(píng)論