新聞中心
前言
我常說(shuō)學(xué)習(xí)一定要有目的,首先發(fā)現(xiàn)問(wèn)題,或者不便之處,然后尋找解決方案,解決方案可能有很多,我們要選擇好的方法來(lái)使用

10多年的多倫網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營(yíng)銷(xiāo)型網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶(hù)設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整多倫建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“多倫網(wǎng)站設(shè)計(jì)”,“多倫網(wǎng)站推廣”以來(lái),每個(gè)客戶(hù)項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
這篇文章介紹JDK8推出的Optional容器,會(huì)從以下幾點(diǎn)展開(kāi):
- 現(xiàn)在編程的問(wèn)題或者說(shuō)痛點(diǎn)是什么
- 通過(guò)案例演示:解決方案有哪些,Optional怎么解決
- Optional系統(tǒng)用法
- Optional的錯(cuò)誤用法
- Optional總結(jié)
由此一起來(lái)認(rèn)識(shí)Optional的正確使用方式,遠(yuǎn)比我們想象的強(qiáng)大,好用,看很多文章和教程都在講API,個(gè)人感覺(jué)調(diào)用一個(gè)方法誰(shuí)不會(huì)?它到底好在哪才是最重要的,我發(fā)布的文章都秉承發(fā)現(xiàn)問(wèn)題,解決問(wèn)題的理念展開(kāi),好了,不吹了,精彩的要來(lái)了!
編程痛點(diǎn)
作為Java程序員遇到NullPointerException是非常痛苦的,這可能是我們遇到的最多的異常了
前后端聯(lián)調(diào):嗨!哥們,你這500啥意思呀?
后端:先是沉思,這怎么會(huì)有空指針?對(duì)前端說(shuō):哥們等1分鐘,馬上解決,我可不能說(shuō)空指針,我可是老開(kāi)發(fā)了!說(shuō)空指針多沒(méi)面子。
產(chǎn)生過(guò)這種無(wú)奈的請(qǐng)?jiān)谠u(píng)論區(qū)大聲說(shuō)出來(lái)!無(wú)論是新手還是專(zhuān)家,在NullPointerException面前可謂眾生平等
我們編程時(shí)經(jīng)常承受:寫(xiě)了類(lèi)型檢查,值判斷,最終沒(méi)想到竟然是一個(gè)null的痛苦,毫不留情的甩出來(lái)一個(gè)令人厭煩的NullPointerException,比如:
系統(tǒng)中用戶(hù),有些用戶(hù)進(jìn)行了實(shí)名認(rèn)證,擁有身份證信息,有些用戶(hù)并沒(méi)有完成實(shí)名認(rèn)證就沒(méi)有身份證信息【不要深究設(shè)計(jì)是否合理,僅僅是舉例講解Optional知識(shí)點(diǎn)】
用戶(hù)類(lèi):
public class User {
private Long id;
private String name;
// 身份證對(duì)象
private IdCard idCard;
// getter、setter、toString
}身份證類(lèi):
public class IdCard {
private Long id;
// 身份證號(hào)碼
private String idNum;
// getter、setter、toString
}測(cè)試類(lèi):獲取用戶(hù)的身份證號(hào)碼
public class OptionalMain {
public static void main(String[] args) {
// 創(chuàng)建用戶(hù)對(duì)象
User user = new User();
// 調(diào)用一系列g(shù)et方法獲取身份證號(hào)碼
// 因?yàn)檎{(diào)用 getIdCard()時(shí)并沒(méi)有身份證對(duì)象為null,再調(diào)用getIdNum方法則出現(xiàn) NullPointerException
String idNum = user.getIdCard().getIdNum();
System.out.println(idNum);
}
}運(yùn)行結(jié)果:
如果user是傳遞進(jìn)來(lái)的,傳進(jìn)來(lái)的user也有可能是null
解決方案
怎樣做才能避免不期而至的NullPointerException?通常,在需要的地方添加null的檢查,所以我們的代碼多了很多的判斷是否為null的驗(yàn)證,影響代碼結(jié)構(gòu),甚至有時(shí)不加思索是否需要驗(yàn)證也會(huì)統(tǒng)一加上非空判斷,來(lái)避免不可預(yù)知的空值,防止生產(chǎn)環(huán)境造成損失!并且添加的方式往往各有不同:
嵌套判斷:
public class OptionalMain {
public static void main(String[] args) {
User user = new User();
// 判斷user是否為null
if(user != null) {
IdCard idCard = user.getIdCard();
// 判斷 idCard 是否為null
if(idCard != null) {
// 獲取身份證號(hào)碼
System.out.println(idCard.getIdNum());
}else {
System.out.println("未實(shí)名認(rèn)證!");
}
}else {
System.out.println("該用戶(hù)不存在!");
}
}
}逐個(gè)判斷:
public class OptionalMain {
/**
* 獲取身份證號(hào)碼
* @param user:用戶(hù)
* @return:身份證號(hào)碼
*/
public static String getUserIdcardNum(User user) {
// 判斷用戶(hù)是否為空
if(user == null) {
return "無(wú)此用戶(hù)";
}
// 判斷是否實(shí)名認(rèn)證
if(user.getIdCard() == null) {
return "該用戶(hù)未實(shí)名認(rèn)證";
}
// 返回身份證號(hào)碼,如果:要對(duì)身份證號(hào)碼進(jìn)行操作,也要對(duì)idNum進(jìn)行非空判斷
return user.getIdCard().getIdNum();
}
public static void main(String[] args) {
// 創(chuàng)建用戶(hù)對(duì)象
User user = new User();
// 1、調(diào)用獲取身份證方法,有用戶(hù)但未實(shí)名
System.out.println("******未認(rèn)證******");
String userIdcardNum1 = getUserIdcardNum(user);
System.out.println("結(jié)果:" + userIdcardNum1);
// 2、傳遞空用戶(hù)
System.out.println("******空用戶(hù)******");
String userIdcardNum2 = getUserIdcardNum(null);
System.out.println("結(jié)果:" + userIdcardNum2);
// 3、創(chuàng)建身份證對(duì)象
IdCard idCard = new IdCard();
idCard.setId(1L);
idCard.setIdNum("411481199611111516");
user.setIdCard(idCard);
// 傳遞實(shí)名認(rèn)證的用戶(hù)
System.out.println("******已認(rèn)證******");
String userIdcardNum3 = getUserIdcardNum(user);
System.out.println("結(jié)果:" + userIdcardNum3);
}
}運(yùn)行結(jié)果:
如果有其他要求,就要做更多的非空判斷,影響代碼的連貫性,凈判斷空值了
一旦忘記判斷某一個(gè)值是否為空,就又要和 NullPointerException 偶遇了,它并不是女朋友,而是最不想遇見(jiàn)的【債主】
null值帶來(lái)的問(wèn)題
- NullPointerException是目前Java程序開(kāi)發(fā)中最典型的異常,有些書(shū)中稱(chēng)其為錯(cuò)誤之源,個(gè)人覺(jué)得有點(diǎn)夸張,你覺(jué)著呢?
- 各種非空判斷,讓代碼變的冗余,閱讀性很糟糕,非空判斷對(duì)業(yè)務(wù)實(shí)現(xiàn)是毫無(wú)意義的
- null值本身也毫無(wú)意義,可以認(rèn)為是給對(duì)象一個(gè)【錯(cuò)誤的默認(rèn)值】
- null可以被賦值給任意的引用數(shù)據(jù)類(lèi)型,如果是分布式系統(tǒng),該值被傳遞到另一個(gè)服務(wù)中,無(wú)法知道最初的它是什么類(lèi)型,也無(wú)法對(duì)其進(jìn)行賦值
- Java為了簡(jiǎn)化語(yǔ)言,摒棄了指針的概念,但是 NullPointerException是個(gè)例外
Optional的出現(xiàn)
Java團(tuán)隊(duì)結(jié)合Haskell和Scala語(yǔ)言對(duì)null值的處理方式,在JDK8時(shí)推出Optional類(lèi)來(lái)專(zhuān)門(mén)處理空值問(wèn)題,當(dāng)然該類(lèi)并不是為了避免我們?nèi)?xiě)!=null的非空判斷,他功能很強(qiáng),配合Lambda表達(dá)式更香
源碼注釋
/**
* A container object which may or may not contain a non-null value.
一個(gè)可以包含或不包含非空值的容器對(duì)象
* If a value is present, {@code isPresent()} will return {@code true} and
* {@code get()} will return the value.
如果存在值,isPresent()方法會(huì)返回true,通過(guò)get()方法返回值
*
*Additional methods that depend on the presence or absence of a contained
* value are provided, such as {@link #orElse(java.lang.Object) orElse()}
* (return a default value if value not present) and
* {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
* of code if the value is present).
提供了取決于是否存在包含值的其他方法,比如orElse,如果值不存在,則返回默認(rèn)值 并且 可以通過(guò)ifPresent()
判斷值是否存在,存在則執(zhí)行代碼塊
*This is a value-based
* class; use of identity-sensitive operations (including reference equality
* ({@code ==}), identity hash code, or synchronization) on instances of
* {@code Optional} may have unpredictable results and should be avoided.
這是一個(gè)基于值的類(lèi),應(yīng)避免使用于身份敏感操作【這里應(yīng)該意思是:對(duì)象是否存在不確定的敏感操作】(包括引用 ==,哈?;蛲剑┑膶?shí)例可能會(huì)產(chǎn)生不可預(yù)測(cè)的結(jié)果
* @since
從Optional類(lèi)的定義和聲明來(lái)看特點(diǎn)如下:
- 是一個(gè)final類(lèi),不可被繼承,并且是一個(gè)泛型類(lèi)
- 該類(lèi)是一個(gè)容器,可以用來(lái)存儲(chǔ)對(duì)象
- 該類(lèi)提供了一系列方法來(lái)判斷是否有值【isPresent()】和獲取值【get()】
Optional解決null問(wèn)題
通過(guò)案例感受Optional處理null的套路:
- 將可能為null,或者說(shuō)允許為null的數(shù)據(jù)存儲(chǔ)進(jìn)Optional容器中
- 通過(guò)Optional的map、filter、flatMap方法對(duì)數(shù)據(jù)進(jìn)行處理,獲取需要的對(duì)象屬性,用法和Stream相同
- 如果數(shù)據(jù)為空了,可以返回一個(gè)自定義對(duì)象,或者拋出異常都可以,隨你所愿
User類(lèi):
public class User {
private Long id;
private String name;
// 將可能為null的對(duì)象放入Optional中
private Optional idCard;
// getter、setter、toString
} IdCard類(lèi):
public class IdCard {
private Long id;
// 如果身份證號(hào)碼也允許為null,也可以放入Optional中【Optional】
// 但是實(shí)名認(rèn)證了,身份證號(hào)碼就是必須的了不是嗎,
// 一旦使用了Optional,沒(méi)有身份證號(hào)碼時(shí),也不會(huì)出現(xiàn)報(bào)錯(cuò),可能會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)誤,所以也不要濫用
private String idNum;
// getter、setter、toString
} 測(cè)試類(lèi):
public class OptionalMain {
/**
* 獲取身份證號(hào)碼
* @param user:用戶(hù)
* @return:身份證號(hào)碼
*/
public static String getUserIdcardNum(User user){
// 將User通過(guò)Optional.of() 方法 存儲(chǔ)進(jìn)Optional
Optional optionalUser = Optional.of(user);
// 通過(guò)map方法先獲取user中身份對(duì)象,orElse:如果沒(méi)有,返回一個(gè)自定義的Optional對(duì)象
Optional optionalIdCard = optionalUser.map(User::getIdCard).orElse(Optional.of(new IdCard()));
// 通過(guò)map方法獲取IdCard中的idNum,如果沒(méi)有返回 "無(wú)實(shí)名認(rèn)證"字符串
String idNum = optionalIdCard.map(IdCard::getIdNum).orElse("無(wú)實(shí)名認(rèn)證");
return idNum;
}
public static void main(String[] args){
User user = new User();
// 將user對(duì)象傳進(jìn)方法中,該對(duì)象中的IdCard為null
System.out.println(getUserIdcardNum(user));
}
} 運(yùn)行結(jié)果:
我們僅僅傳入了user對(duì)象,IdCard為null,通過(guò)getUserIdcardNum方法處理之后,返回定義的無(wú)實(shí)名認(rèn)證,這里并沒(méi)有做if...else的判斷,這樣的代碼看起來(lái)更優(yōu)雅,不是嗎?
總結(jié)來(lái)說(shuō):
- 把對(duì)象放進(jìn)Optional中,可以通過(guò)Optional提供的API來(lái)操作容器中的對(duì)象
- 如:對(duì)象非空正常使用,我們可以通過(guò)get()方法獲取對(duì)象
- 如果是空可以通過(guò)某些方法【orElse、orElseGet、orElseThrow】,返回自定義的結(jié)果,避免空指針異常出現(xiàn)
- 通過(guò)一些判斷方法來(lái)判斷Optional中對(duì)象是否為null
接下來(lái)講解一下Optional中的API,系統(tǒng)認(rèn)識(shí),學(xué)習(xí)強(qiáng)大的Optional
Optional結(jié)構(gòu)
Optional方法概覽
|
方法 |
作用 |
|
Optional.empty() |
創(chuàng)建一個(gè)空的 Optional 實(shí)例 |
|
Optional.of(T t) |
創(chuàng)建一個(gè) Optional 實(shí)例,當(dāng) t為null時(shí)拋出異常 |
|
Optional.ofNullable(T t) |
創(chuàng)建一個(gè) Optional 實(shí)例,但當(dāng) t為null時(shí)不會(huì)拋出異常,而是返回一個(gè)空的實(shí)例 |
|
get() |
獲取optional實(shí)例中的對(duì)象,當(dāng)optional 容器為空時(shí)報(bào)錯(cuò) |
|
isPresent() |
判斷optional是否為空,如果空則返回false,否則返回true |
|
ifPresent(Consumer c) |
如果optional不為空,則將optional中的對(duì)象傳給Comsumer函數(shù) |
|
orElse(T other) |
如果optional不為空,則返回optional中的對(duì)象;如果為null,則返回 other 這個(gè)默認(rèn)值 |
|
orElseGet(Supplier |
如果optional不為空,則返回optional中的對(duì)象;如果為null,則使用Supplier函數(shù)生成默認(rèn)值other |
|
orElseThrow(Supplier |
如果optional不為空,則返回optional中的對(duì)象;如果為null,則拋出Supplier函數(shù)生成的異常 |
|
filter(Predicate |
如果optional不為空,則執(zhí)行斷言函數(shù)p,如果p的結(jié)果為true,則返回原本的optional,否則返回空的optional |
|
map(Function |
如果optional不為空,則將optional中的對(duì)象 t 映射成另外一個(gè)對(duì)象 u,并將 u 存放到一個(gè)新的optional容器中 |
|
flatMap(Function< T,Optional> mapper) |
跟上面一樣,在optional不為空的情況下,將對(duì)象t映射成另外一個(gè)optional,區(qū)別在于:map會(huì)自動(dòng)將u放到optional中,而flatMap則需要手動(dòng)給u創(chuàng)建一個(gè)optional |
強(qiáng)烈建議:打開(kāi)編輯器,多翻閱源碼,對(duì)學(xué)習(xí)和編碼都有很大幫助,剛開(kāi)始看不懂沒(méi)關(guān)系,量變產(chǎn)生質(zhì)變
Optional 創(chuàng)建
通過(guò)Optional源碼發(fā)現(xiàn):
- 該類(lèi)final修飾,不能被繼承,只有一個(gè)Object父類(lèi)
- 是一個(gè)泛型類(lèi),使用時(shí)為了類(lèi)型安全指明泛型類(lèi)型
- 連個(gè)私有常量,供內(nèi)部調(diào)用,其中value為Optional容器中存儲(chǔ)的對(duì)象
- 兩個(gè)構(gòu)造方法,無(wú)參和有參的都為私有,說(shuō)明不能通過(guò)構(gòu)造方法創(chuàng)建Optional對(duì)象,需要通過(guò)內(nèi)部提供的【empty()、of(T t)、ofNullable(T t)】三個(gè)靜態(tài)方法創(chuàng)建,這種創(chuàng)建方式其實(shí)就是【工廠模式】
代碼實(shí)現(xiàn):
// 創(chuàng)建一個(gè)包裝對(duì)象值為空的Optional對(duì)象
Optional
Optional其他API
get()
作用:獲取optional實(shí)例中的對(duì)象,當(dāng)optional 容器為空時(shí)報(bào)錯(cuò)
源碼:
- 判斷value是否為null
- 為null,拋出 NoSuchElementException("No value present")異常
- 不為null,返回value
null值Optional:
// 創(chuàng)建值為null的Optional對(duì)象
Optionaloptional = Optional.empty();
// get返回 NoSuchElementException("No value present")
String result = optional.get();
System.out.println(result);
非null值Optional:
// 創(chuàng)建值為:optional的Optional對(duì)象
Optionaloptional = Optional.of("optional");
// 返回值 optional
String result = optional.get();
System.out.println(result);
isPresent()
作用:判斷optional是否為空,如果空則返回false,否則返回true
源碼:
代碼實(shí)現(xiàn):
Listusers = new ArrayList<>();
users.add("柯南");
users.add("佩奇");
users.add("喜洋洋");
Optional> optional = Optional.of(users);
// 判斷并消費(fèi)
optional.ifPresent(System.out::println);
orElse(T other)
作用:如果optional不為空,則返回optional中的對(duì)象;如果為null,則返回 other 這個(gè)默認(rèn)值
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
// 1、存儲(chǔ)非null數(shù)據(jù)
OptionaluserOptional = Optional.ofNullable(user);
// 獲取用戶(hù)名
String name1 = userOptional.orElse(new User(0L, "帥氣添甄")).getName();
// 非null,結(jié)果為:格雷福斯
System.out.println(name1);
// 2、存儲(chǔ)null數(shù)據(jù)
OptionalnullOptional = Optional.ofNullable(null);
String name2 = nullOptional.orElse(new User(0L, "帥氣添甄")).getName();
// 為null,結(jié)果:帥氣添甄
System.out.println(name2);
orElseGet(Supplierother)
作用:如果optional不為空,則返回optional中的對(duì)象;如果為null,則使用Supplier函數(shù)生成默認(rèn)值other
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 為null直接返回`Supplier`生產(chǎn)型函數(shù)接口返回的對(duì)象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不為空,則返回optional中的對(duì)象;如果為null,則拋出Supplier函數(shù)生成的異常
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 為null直接返回`Supplier`生產(chǎn)型函數(shù)接口返回的對(duì)象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不為空,則返回optional中的對(duì)象;如果為null,則拋出Supplier函數(shù)生成的異常
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 如果數(shù)據(jù)為null,拋出 指定異常
String name = userOptional.orElseThrow(() new RuntimeException("無(wú)數(shù)據(jù)")).getName();
System.out.println(name);
filter(Predicatep)
作用:如果optional不為空,則執(zhí)行斷言函數(shù)p,如果p的結(jié)果為true,則返回原本的optional,否則返回空的optional
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 過(guò)濾名字長(zhǎng)度大于3,如果有值才輸出,沒(méi)值就不輸出
userOptional.filter(item -> item.getName().length() > 3).ifPresent(System.out::println);
map(Function mapper)
作用:如果optional不為空,則將optional中的對(duì)象 t 映射成另外一個(gè)對(duì)象 u,并將 u 存放到一個(gè)新的optional容器中,該方法與Stream的map作用一樣
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 只獲取用戶(hù)名
String name = userOptional.map(User::getName).orElse("添甄");
System.out.println(name);
flatMap(Function< T,Optional> mapper)
作用:在optional不為空的情況下,將對(duì)象t映射成另外一個(gè)optional,17-flatMapmap接收的是U類(lèi)型,而flatMap接收的是Optional類(lèi)型,返回也是需要放進(jìn)Optional中
源碼:
代碼實(shí)現(xiàn):
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(null);
Optionaloptional = userOptional.flatMap(item -> Optional.ofNullable(item.getName()));
String name = optional.orElse("添甄");
System.out.println(name);
錯(cuò)誤示范
獲取用戶(hù)名:
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
// 判斷是否有值
if (userOptional.isPresent()) {
String name = userOptional.get().getName();
System.out.println(name);
}else {
System.out.println("無(wú)值");
}
通過(guò)調(diào)用isPresent方法判斷是否有值,這還是增加了判斷,破壞代碼結(jié)構(gòu)
正確姿勢(shì):
多用map,orElse,filter方法發(fā)揮Optional的作用
User user = new User(1L,"格雷福斯");
OptionaluserOptional = Optional.ofNullable(user);
String name = userOptional.map(User::getName).orElse("無(wú)值");
System.out.println(name);
總結(jié)
- Optional是一個(gè)用來(lái)解決null值,避免發(fā)生空指針異常的容器,配合Lambda表達(dá)式寫(xiě)出優(yōu)雅代碼
- 靜態(tài)工廠方法Optional.empty()、Optional.of()以及Optional.ofNullable()創(chuàng)建Optional對(duì)象
- Optional類(lèi)包含多種方法,其中map、flatMap、filter,它們?cè)诟拍钌吓cStream類(lèi)中對(duì)應(yīng)的方法十分相似
- 使用Optional能幫助你開(kāi)發(fā)出更便于閱讀和簡(jiǎn)介的程序
- 多使用Optional中的方法給定默認(rèn)值,比如map、orElse等方法來(lái)避免過(guò)多的if判斷
文章出自:??石添的編程哲學(xué)??,如有轉(zhuǎn)載本文請(qǐng)聯(lián)系【石添的編程哲學(xué)】今日頭條號(hào)。
網(wǎng)站欄目:正確使用Java8中的Optional,它遠(yuǎn)比我們想象的優(yōu)秀
文章路徑:http://www.dlmjj.cn/article/cocoioj.html


咨詢(xún)
建站咨詢(xún)
