0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

JAVA8提供了Optional類來優(yōu)化這種寫法

Linux愛好者 ? 來源:CSDN ? 作者:zjhred ? 2022-04-24 15:18 ? 次閱讀

引言

在文章的開頭,先說下NPE問題,NPE問題就是,我們在開發(fā)中經(jīng)常碰到的NullPointerException.假設(shè)我們有兩個類,他們的UML類圖如下圖所示

9b4c56a8-c39e-11ec-bce3-dac502259ad0.png

在這種情況下,有如下代碼

user.getAddress().getProvince();

這種寫法,在user為null時,是有可能報(bào)NullPointerException異常的。為了解決這個問題,于是采用下面的寫法

if(user!=null){
Addressaddress=user.getAddress();
if(address!=null){
Stringprovince=address.getProvince();
}
}

這種寫法是比較丑陋的,為了避免上述丑陋的寫法,讓丑陋的設(shè)計(jì)變得優(yōu)雅。JAVA8提供了Optional類來優(yōu)化這種寫法,接下來的正文部分進(jìn)行詳細(xì)說明

一個連載多年還在繼續(xù)更新的免費(fèi)教程:http://blog.didispace.com/spring-boot-learning-2x/

API介紹

先介紹一下API,與其他文章不同的是,本文采取類比的方式來講,同時結(jié)合源碼。而不像其他文章一樣,一個個API羅列出來,讓人找不到重點(diǎn)。

1、Optional(T value),empty(),of(T value),ofNullable(T value)

這四個函數(shù)之間具有相關(guān)性,因此放在一組進(jìn)行記憶。

先說明一下,Optional(T value),即構(gòu)造函數(shù),它是private權(quán)限的,不能由外部調(diào)用的。其余三個函數(shù)是public權(quán)限,供我們所調(diào)用。那么,Optional的本質(zhì),就是內(nèi)部儲存了一個真實(shí)的值,在構(gòu)造的時候,就直接判斷其值是否為空。好吧,這么說還是比較抽象。直接上Optional(T value)構(gòu)造函數(shù)的源碼,如下圖所示

9b5ee322-c39e-11ec-bce3-dac502259ad0.png

那么,of(T value)的源碼如下

publicstaticOptionalof(Tvalue){
returnnewOptional<>(value);
}

也就是說of(T value)函數(shù)內(nèi)部調(diào)用了構(gòu)造函數(shù)。根據(jù)構(gòu)造函數(shù)的源碼我們可以得出兩個結(jié)論:

  • 通過of(T value)函數(shù)所構(gòu)造出的Optional對象,當(dāng)Value值為空時,依然會報(bào)NullPointerException。
  • 通過of(T value)函數(shù)所構(gòu)造出的Optional對象,當(dāng)Value值不為空時,能正常構(gòu)造Optional對象。

除此之外呢,Optional類內(nèi)部還維護(hù)一個value為null的對象,大概就是長下面這樣的

publicfinalclassOptional<T>{
//省略....
privatestaticfinalOptionalEMPTY=newOptional<>();
privateOptional(){
this.value=null;
}
//省略...
publicstaticOptionalempty(){
@SuppressWarnings("unchecked")
Optionalt=(Optional)EMPTY;
returnt;
}
}

那么,empty()的作用就是返回EMPTY對象。

好了鋪墊了這么多,可以說ofNullable(T value)的作用了,上源碼

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

好吧,大家應(yīng)該都看得懂什么意思了。相比較of(T value)的區(qū)別就是,當(dāng)value值為null時,of(T value)會報(bào)NullPointerException異常;ofNullable(T value)不會throw Exception,ofNullable(T value)直接返回一個EMPTY對象。

那是不是意味著,我們在項(xiàng)目中只用ofNullable函數(shù)而不用of函數(shù)呢?

不是的,一個東西存在那么自然有存在的價(jià)值。當(dāng)我們在運(yùn)行過程中,不想隱藏NullPointerException。而是要立即報(bào)告,這種情況下就用Of函數(shù)。但是不得不承認(rèn),這樣的場景真的很少。博主也僅在寫junit測試用例中用到過此函數(shù)。

2、orElse(T other),orElseGet(Supplier other)和orElseThrow(Supplier exceptionSupplier)

這三個函數(shù)放一組進(jìn)行記憶,都是在構(gòu)造函數(shù)傳入的value值為null時,進(jìn)行調(diào)用的。orElseorElseGet的用法如下所示,相當(dāng)于value值為null時,給予一個默認(rèn)值:

@Test
publicvoidtest(){
Useruser=null;
user=Optional.ofNullable(user).orElse(createUser());
user=Optional.ofNullable(user).orElseGet(()->createUser());

}
publicUsercreateUser(){
Useruser=newUser();
user.setName("zhangsan");
returnuser;
}

這兩個函數(shù)的區(qū)別:當(dāng)user值不為null時,orElse函數(shù)依然會執(zhí)行createUser()方法,而orElseGet函數(shù)并不會執(zhí)行createUser()方法,大家可自行測試。

至于orElseThrow,就是value值為null時,直接拋一個異常出去,用法如下所示

Useruser=null;
Optional.ofNullable(user).orElseThrow(()->newException("用戶不存在"));

3、map(Function mapper)和flatMap(Function> mapper)

這兩個函數(shù)放在一組記憶,這兩個函數(shù)做的是轉(zhuǎn)換值的操作。

直接上源碼

publicfinalclassOptional<T>{
//省略....
publicOptionalmap(FunctionsuperT,?extendsU>mapper){
Objects.requireNonNull(mapper);
if(!isPresent())
returnempty();
else{
returnOptional.ofNullable(mapper.apply(value));
}
}
//省略...
publicOptionalflatMap(FunctionsuperT,Optional>mapper){
Objects.requireNonNull(mapper);
if(!isPresent())
returnempty();
else{
returnObjects.requireNonNull(mapper.apply(value));
}
}
}

這兩個函數(shù),在函數(shù)體上沒什么區(qū)別。唯一區(qū)別的就是入?yún)?,map函數(shù)所接受的入?yún)㈩愋蜑?code style="font-size:14px;padding:2px 4px;margin-right:2px;margin-left:2px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">Function,而flapMap的入?yún)㈩愋蜑?code style="font-size:14px;padding:2px 4px;margin-right:2px;margin-left:2px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">Function>。

在具體用法上,對于map而言:

如果User結(jié)構(gòu)是下面這樣的

publicclassUser{
privateStringname;
publicStringgetName(){
returnname;
}
}

這時候取name的寫法如下所示

Stringcity=Optional.ofNullable(user).map(u->u.getName()).get();

對于flatMap而言:

如果User結(jié)構(gòu)是下面這樣的

publicclassUser{
privateStringname;
publicOptionalgetName(){
returnOptional.ofNullable(name);
}
}

這時候取name的寫法如下所示

Stringcity=Optional.ofNullable(user).flatMap(u->u.getName()).get();

4、isPresent()和ifPresent(Consumer consumer)

這兩個函數(shù)放在一起記憶,isPresent即判斷value值是否為空,而ifPresent就是在value值不為空時,做一些操作。這兩個函數(shù)的源碼如下

publicfinalclassOptional<T>{
//省略....
publicbooleanisPresent(){
returnvalue!=null;
}
//省略...
publicvoidifPresent(ConsumersuperT>consumer){
if(value!=null)
consumer.accept(value);
}
}

需要額外說明的是,大家千萬不要把

if(user!=null){
//TODO:dosomething
}

給寫成

Useruser=Optional.ofNullable(user);
if(Optional.isPresent()){
//TODO:dosomething
}

因?yàn)檫@樣寫,代碼結(jié)構(gòu)依然丑陋。博主會在后面給出正確寫法

至于ifPresent(Consumer consumer),用法也很簡單,如下所示

Optional.ofNullable(user).ifPresent(u->{
//TODO:dosomething
});

5、filter(Predicate predicate)

不多說,直接上源碼

publicfinalclassOptional<T>{
//省略....
Objects.requireNonNull(predicate);
if(!isPresent())
returnthis;
else
returnpredicate.test(value)?this:empty();
}

filter 方法接受一個 Predicate 來對 Optional 中包含的值進(jìn)行過濾,如果包含的值滿足條件,那么還是返回這個 Optional;否則返回 Optional.empty。

用法如下

Optionaluser1=Optional.ofNullable(user).filter(u->u.getName().length()<6);

如上所示,如果user的name的長度是小于6的,則返回。如果是大于6的,則返回一個EMPTY對象。

一個連載多年還在繼續(xù)更新的免費(fèi)教程:http://blog.didispace.com/spring-boot-learning-2x/

實(shí)戰(zhàn)使用

例一

在函數(shù)方法中

以前寫法

publicStringgetCity(Useruser)throwsException{
if(user!=null){
if(user.getAddress()!=null){
Addressaddress=user.getAddress();
if(address.getCity()!=null){
returnaddress.getCity();
}
}
}
thrownewExcpetion("取值錯誤");
}

JAVA8寫法

publicStringgetCity(Useruser)throwsException{
returnOptional.ofNullable(user)
.map(u->u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->newException("取指錯誤"));
}

例二

比如,在主程序中

以前寫法

if(user!=null){
dosomething(user);
}

JAVA8寫法

Optional.ofNullable(user)
.ifPresent(u->{
dosomething(u);
});

例三

以前寫法

publicUsergetUser(Useruser)throwsException{
if(user!=null){
Stringname=user.getName();
if("zhangsan".equals(name)){
returnuser;
}
}else{
user=newUser();
user.setName("zhangsan");
returnuser;
}
}

java8寫法

publicUsergetUser(Useruser){
returnOptional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()->{
Useruser1=newUser();
user1.setName("zhangsan");
returnuser1;
});
}

其他的例子,不一一列舉了。不過采用這種鏈?zhǔn)?a href="http://ttokpm.com/v/tag/1315/" target="_blank">編程,雖然代碼優(yōu)雅了。但是,邏輯性沒那么明顯,可讀性有所降低,大家項(xiàng)目中看情況酌情使用。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104489
  • API
    API
    +關(guān)注

    關(guān)注

    2

    文章

    1473

    瀏覽量

    61752

原文標(biāo)題:Java8 判空新寫法!

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    java子類可以繼承父的什么

    Java是一種面向?qū)ο蟮木幊陶Z言,它支持繼承的概念。繼承是面向?qū)ο缶幊痰娜筇匦灾?,它允許一個(稱為子類或派生)繼承另一個(稱為父
    的頭像 發(fā)表于 08-20 11:02 ?780次閱讀

    JDK8升級JDK11最全實(shí)踐干貨來了

    1、前言 截至目前(2023年),Java8發(fā)布至今已有9年,2018年9月25日,Oracle發(fā)布Java11,這是Java8之后的首個LTS版本。那么從JDK
    的頭像 發(fā)表于 06-25 14:51 ?378次閱讀
    JDK<b class='flag-5'>8</b>升級JDK11最全實(shí)踐干貨來了

    java實(shí)現(xiàn)多線程的幾種方式

    多種實(shí)現(xiàn)多線程的方式,本文將詳細(xì)介紹以下幾種方式: 1.繼承Thread 2.實(shí)現(xiàn)Runnable接口 3.Callable和Future 4.線程池 5.Java 8
    的頭像 發(fā)表于 03-14 16:55 ?550次閱讀

    Android編譯優(yōu)化之混淆配置

    為了使用java8及后續(xù)java新版本的特性,Google增加了一步編譯過程—脫糖(desugaring),但這一步會導(dǎo)致更長的編譯時間,這也是為什么Google會推出D8和R8編譯器
    的頭像 發(fā)表于 12-21 09:21 ?1960次閱讀
    Android編譯<b class='flag-5'>優(yōu)化</b>之混淆配置

    Java各種類的區(qū)別

    8種基本類型:byte、short、int、long、float、double、char和boolean。這些基本類型都有對應(yīng)的包裝,用于將基本數(shù)據(jù)類型轉(zhuǎn)化為對象。 包裝: 包裝
    的頭像 發(fā)表于 12-03 11:08 ?7605次閱讀

    java有比long還大的類型嗎

    。 BigDecimal 類型: BigDecimal 類型是 Java 提供的一個用于表示任意大精度小數(shù)的。它使用內(nèi)部的 BigInteger
    的頭像 發(fā)表于 11-30 11:25 ?2866次閱讀

    Java提供哪些注釋語句

    Java提供很多種類型的注釋語句,它們都有不同的用途和作用。在本文中,我們將詳細(xì)介紹Java中的注釋語句類型,并討論它們的用法和如何正確使用它們。 單行注釋(Single-line
    的頭像 發(fā)表于 11-28 16:56 ?566次閱讀

    java源程序是由定義組成的嗎

    Java源程序是由定義組成的。在Java中,是用來定義對象的一種結(jié)構(gòu)化方式。一個可以包含字段(變量)和方法(函數(shù)),它們一起描述
    的頭像 發(fā)表于 11-28 16:36 ?604次閱讀

    java的主必須是public

    一下Java程序的基本結(jié)構(gòu)。一個Java程序可以包含多個,但是只能有一個公共的作為主。這個主
    的頭像 發(fā)表于 11-28 16:15 ?5333次閱讀

    java中常用的包有哪些

    Java是一種面向?qū)ο蟮母呒壘幊陶Z言,它具有平臺無關(guān)性和可擴(kuò)展性。Java中有很多常用的包,這些包提供豐富的庫和工具,用于開發(fā)各種類型的
    的頭像 發(fā)表于 11-22 15:10 ?1288次閱讀

    java導(dǎo)入包import怎么寫

    Java中,使用import語句引入其他包中的,以便在當(dāng)前文件中使用這些。import語句可以幫助我們有效地組織和管理代碼,并且使程序更易讀和易維護(hù)。下面是關(guān)于
    的頭像 發(fā)表于 11-22 15:00 ?919次閱讀

    java接口是特殊的抽象

    Java接口是一種特殊的抽象嗎? 在Java編程語言中,接口(interface)與抽象(abstract class)是兩種不同的概念,盡管它們之間存在一些相似之處,但在設(shè)計(jì)和使
    的頭像 發(fā)表于 11-21 10:07 ?587次閱讀

    Java中抽象和接口的介紹

    Java是一種面向?qū)ο蟮木幊陶Z言,提供抽象和接口兩種機(jī)制幫助開發(fā)人員組織和管理代碼。本文將詳細(xì)介紹
    的頭像 發(fā)表于 11-21 09:47 ?429次閱讀

    java接口和抽象的區(qū)別

    接口和抽象的區(qū)別,包括定義、實(shí)現(xiàn)、字段、構(gòu)造函數(shù)、繼承、多態(tài)、默認(rèn)方法等方面。 定義 接口表示一種契約,它只是一種規(guī)范或協(xié)議,定義應(yīng)該包含哪些方法以及它們的簽名,但沒有提供方法的
    的頭像 發(fā)表于 11-21 09:45 ?498次閱讀

    insertinto語句的三種寫法

    寫法,它們分別是: 省略列名寫法: INSERT INTO table_name VALUES (value1, value2, ...); 這種寫法是最簡單的方式,它忽略
    的頭像 發(fā)表于 11-17 15:12 ?3531次閱讀