前言
我常說學習一定要有目的,首先發現問題,或者不便之處,然后尋找解決方案,解決方案可能有很多,我們要選擇好的方法來使用
這篇文章介紹JDK8推出的Optional容器,會從以下幾點展開:
- 現在編程的問題或者說痛點是什么
- 通過案例演示:解決方案有哪些,Optional怎么解決
- Optional系統用法
- Optional的錯誤用法
- Optional總結
由此一起來認識Optional的正確使用方式,遠比我們想象的強大,好用,看很多文章和教程都在講API,個人感覺調用一個方法誰不會?它到底好在哪才是最重要的,我發布的文章都秉承發現問題,解決問題的理念展開,好了,不吹了,精彩的要來了!
編程痛點
作為Java程序員遇到NullPointerException是非常痛苦的,這可能是我們遇到的最多的異常了
前后端聯調:嗨!哥們,你這500啥意思呀?
后端:先是沉思,這怎么會有空指針?對前端說:哥們等1分鐘,馬上解決,我可不能說空指針,我可是老開發了!說空指針多沒面子。
產生過這種無奈的請在評論區大聲說出來!無論是新手還是專家,在NullPointerException面前可謂眾生平等
我們編程時經常承受:寫了類型檢查,值判斷,最終沒想到竟然是一個null的痛苦,毫不留情的甩出來一個令人厭煩的NullPointerException,比如:
系統中用戶,有些用戶進行了實名認證,擁有身份證信息,有些用戶并沒有完成實名認證就沒有身份證信息【不要深究設計是否合理,僅僅是舉例講解Optional知識點】
用戶類:
public class User {
private Long id;
private String name;
// 身份證對象
private IdCard idCard;
// getter、setter、toString
}
身份證類:
public class IdCard {
private Long id;
// 身份證號碼
private String idNum;
// getter、setter、toString
}
測試類:獲取用戶的身份證號碼
public class OptionalMain {
public static void main(String[] args) {
// 創建用戶對象
User user = new User();
// 調用一系列get方法獲取身份證號碼
// 因為調用 getIdCard()時并沒有身份證對象為null,再調用getIdNum方法則出現 NullPointerException
String idNum = user.getIdCard().getIdNum();
System.out.println(idNum);
}
}
運行結果:

如果user是傳遞進來的,傳進來的user也有可能是null
解決方案
怎樣做才能避免不期而至的NullPointerException?通常,在需要的地方添加null的檢查,所以我們的代碼多了很多的判斷是否為null的驗證,影響代碼結構,甚至有時不加思索是否需要驗證也會統一加上非空判斷,來避免不可預知的空值,防止生產環境造成損失!并且添加的方式往往各有不同:
嵌套判斷:
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) {
// 獲取身份證號碼
System.out.println(idCard.getIdNum());
}else {
System.out.println("未實名認證!");
}
}else {
System.out.println("該用戶不存在!");
}
}
}
逐個判斷:
public class OptionalMain {
/**
* 獲取身份證號碼
* @param user:用戶
* @return:身份證號碼
*/
public static String getUserIdcardNum(User user) {
// 判斷用戶是否為空
if(user == null) {
return "無此用戶";
}
// 判斷是否實名認證
if(user.getIdCard() == null) {
return "該用戶未實名認證";
}
// 返回身份證號碼,如果:要對身份證號碼進行操作,也要對idNum進行非空判斷
return user.getIdCard().getIdNum();
}
public static void main(String[] args) {
// 創建用戶對象
User user = new User();
// 1、調用獲取身份證方法,有用戶但未實名
System.out.println("******未認證******");
String userIdcardNum1 = getUserIdcardNum(user);
System.out.println("結果:" + userIdcardNum1);
// 2、傳遞空用戶
System.out.println("******空用戶******");
String userIdcardNum2 = getUserIdcardNum(null);
System.out.println("結果:" + userIdcardNum2);
// 3、創建身份證對象
IdCard idCard = new IdCard();
idCard.setId(1L);
idCard.setIdNum("411481199611111516");
user.setIdCard(idCard);
// 傳遞實名認證的用戶
System.out.println("******已認證******");
String userIdcardNum3 = getUserIdcardNum(user);
System.out.println("結果:" + userIdcardNum3);
}
}
運行結果:

如果有其他要求,就要做更多的非空判斷,影響代碼的連貫性,凈判斷空值了
一旦忘記判斷某一個值是否為空,就又要和 NullPointerException 偶遇了,它并不是女朋友,而是最不想遇見的【債主】
null值帶來的問題
- NullPointerException是目前Java程序開發中最典型的異常,有些書中稱其為錯誤之源,個人覺得有點夸張,你覺著呢?
- 各種非空判斷,讓代碼變的冗余,閱讀性很糟糕,非空判斷對業務實現是毫無意義的
- null值本身也毫無意義,可以認為是給對象一個【錯誤的默認值】
- null可以被賦值給任意的引用數據類型,如果是分布式系統,該值被傳遞到另一個服務中,無法知道最初的它是什么類型,也無法對其進行賦值
- Java為了簡化語言,摒棄了指針的概念,但是 NullPointerException是個例外
Optional的出現
Java團隊結合Haskell和Scala語言對null值的處理方式,在JDK8時推出Optional類來專門處理空值問題,當然該類并不是為了避免我們去寫!=null的非空判斷,他功能很強,配合Lambda表達式更香
源碼注釋
/**
* A container object which may or may not contain a non-null value.
一個可以包含或不包含非空值的容器對象
* If a value is present, {@code isPresent()} will return {@code true} and
* {@code get()} will return the value.
如果存在值,isPresent()方法會返回true,通過get()方法返回值
*
* <p>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,如果值不存在,則返回默認值 并且 可以通過ifPresent()
判斷值是否存在,存在則執行代碼塊
* <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
* 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.
這是一個基于值的類,應避免使用于身份敏感操作【這里應該意思是:對象是否存在不確定的敏感操作】(包括引用 ==,哈希或同步)的實例可能會產生不可預測的結果
* @since

從Optional類的定義和聲明來看特點如下:
- 是一個final類,不可被繼承,并且是一個泛型類
- 該類是一個容器,可以用來存儲對象
- 該類提供了一系列方法來判斷是否有值【isPresent()】和獲取值【get()】
Optional解決null問題
通過案例感受Optional處理null的套路:
- 將可能為null,或者說允許為null的數據存儲進Optional容器中
- 通過Optional的map、filter、flatMap方法對數據進行處理,獲取需要的對象屬性,用法和Stream相同
- 如果數據為空了,可以返回一個自定義對象,或者拋出異常都可以,隨你所愿
User類:
public class User {
private Long id;
private String name;
// 將可能為null的對象放入Optional中
private Optional<IdCard> idCard;
// getter、setter、toString
}
IdCard類:
public class IdCard {
private Long id;
// 如果身份證號碼也允許為null,也可以放入Optional中【Optional<String>】
// 但是實名認證了,身份證號碼就是必須的了不是嗎,
// 一旦使用了Optional,沒有身份證號碼時,也不會出現報錯,可能會出現數據錯誤,所以也不要濫用
private String idNum;
// getter、setter、toString
}
測試類:
public class OptionalMain {
/**
* 獲取身份證號碼
* @param user:用戶
* @return:身份證號碼
*/
public static String getUserIdcardNum(User user){
// 將User通過Optional.of() 方法 存儲進Optional
Optional<User> optionalUser = Optional.of(user);
// 通過map方法先獲取user中身份對象,orElse:如果沒有,返回一個自定義的Optional<IdCard>對象
Optional<IdCard> optionalIdCard = optionalUser.map(User::getIdCard).orElse(Optional.of(new IdCard()));
// 通過map方法獲取IdCard中的idNum,如果沒有返回 "無實名認證"字符串
String idNum = optionalIdCard.map(IdCard::getIdNum).orElse("無實名認證");
return idNum;
}
public static void main(String[] args){
User user = new User();
// 將user對象傳進方法中,該對象中的IdCard為null
System.out.println(getUserIdcardNum(user));
}
}
運行結果:
我們僅僅傳入了user對象,IdCard為null,通過getUserIdcardNum方法處理之后,返回定義的無實名認證,這里并沒有做if...else的判斷,這樣的代碼看起來更優雅,不是嗎?

總結來說:
- 把對象放進Optional中,可以通過Optional提供的API來操作容器中的對象
- 如:對象非空正常使用,我們可以通過get()方法獲取對象
- 如果是空可以通過某些方法【orElse、orElseGet、orElseThrow】,返回自定義的結果,避免空指針異常出現
- 通過一些判斷方法來判斷Optional中對象是否為null
接下來講解一下Optional中的API,系統認識,學習強大的Optional
Optional結構

Optional方法概覽
方法 | 作用 |
Optional.empty() | 創建一個空的 Optional 實例 |
Optional.of(T t) | 創建一個 Optional 實例,當 t為null時拋出異常 |
Optional.ofNullable(T t) | 創建一個 Optional 實例,但當 t為null時不會拋出異常,而是返回一個空的實例 |
get() | 獲取optional實例中的對象,當optional 容器為空時報錯 |
isPresent() | 判斷optional是否為空,如果空則返回false,否則返回true |
ifPresent(Consumer c) | 如果optional不為空,則將optional中的對象傳給Comsumer函數 |
orElse(T other) | 如果optional不為空,則返回optional中的對象;如果為null,則返回 other 這個默認值 |
orElseGet(Supplier<T> other) | 如果optional不為空,則返回optional中的對象;如果為null,則使用Supplier函數生成默認值other |
orElseThrow(Supplier<X> exception) | 如果optional不為空,則返回optional中的對象;如果為null,則拋出Supplier函數生成的異常 |
filter(Predicate<T> p) | 如果optional不為空,則執行斷言函數p,如果p的結果為true,則返回原本的optional,否則返回空的optional |
map(Function<T, U> mapper) | 如果optional不為空,則將optional中的對象 t 映射成另外一個對象 u,并將 u 存放到一個新的optional容器中 |
flatMap(Function< T,Optional<U>> mapper) | 跟上面一樣,在optional不為空的情況下,將對象t映射成另外一個optional,區別在于:map會自動將u放到optional中,而flatMap則需要手動給u創建一個optional |
強烈建議:打開編輯器,多翻閱源碼,對學習和編碼都有很大幫助,剛開始看不懂沒關系,量變產生質變
Optional 創建
通過Optional源碼發現:
- 該類final修飾,不能被繼承,只有一個Object父類
- 是一個泛型類,使用時為了類型安全指明泛型類型
- 連個私有常量,供內部調用,其中value為Optional容器中存儲的對象
- 兩個構造方法,無參和有參的都為私有,說明不能通過構造方法創建Optional對象,需要通過內部提供的【empty()、of(T t)、ofNullable(T t)】三個靜態方法創建,這種創建方式其實就是【工廠模式】

代碼實現:
// 創建一個包裝對象值為空的Optional對象
Optional<Object> optional1 = Optional.empty();
// 創建包裝對象值非空的Optional對象,如果傳入null則出現`NullPointerException`
Optional<String> optional2 = Optional.of("optional");
// 創建包裝對象值允許為空的Optional對象
Optional<Object> optional3 = Optional.ofNullable(null);
Optional其他API
get()
作用:獲取optional實例中的對象,當optional 容器為空時報錯
源碼:
- 判斷value是否為null
- 為null,拋出 NoSuchElementException("No value present")異常
- 不為null,返回value

null值Optional:
// 創建值為null的Optional對象
Optional<String> optional = Optional.empty();
// get返回 NoSuchElementException("No value present")
String result = optional.get();
System.out.println(result);

非null值Optional:
// 創建值為:optional的Optional對象
Optional<String> optional = Optional.of("optional");
// 返回值 optional
String result = optional.get();
System.out.println(result);

isPresent()
作用:判斷optional是否為空,如果空則返回false,否則返回true
源碼:

代碼實現:
List<String> users = new ArrayList<>();
users.add("柯南");
users.add("佩奇");
users.add("喜洋洋");
Optional<List<String>> optional = Optional.of(users);
// 判斷并消費
optional.ifPresent(System.out::println);
orElse(T other)
作用:如果optional不為空,則返回optional中的對象;如果為null,則返回 other 這個默認值
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
// 1、存儲非null數據
Optional<User> userOptional = Optional.ofNullable(user);
// 獲取用戶名
String name1 = userOptional.orElse(new User(0L, "帥氣添甄")).getName();
// 非null,結果為:格雷福斯
System.out.println(name1);
// 2、存儲null數據
Optional<User> nullOptional = Optional.ofNullable(null);
String name2 = nullOptional.orElse(new User(0L, "帥氣添甄")).getName();
// 為null,結果:帥氣添甄
System.out.println(name2);
orElseGet(Supplierother)
作用:如果optional不為空,則返回optional中的對象;如果為null,則使用Supplier函數生成默認值other
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 為null直接返回`Supplier`生產型函數接口返回的對象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不為空,則返回optional中的對象;如果為null,則拋出Supplier函數生成的異常
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 為null直接返回`Supplier`生產型函數接口返回的對象
String name = userOptional.orElseGet(() new User(0L, "添甄")).getName();
System.out.println(name);
orElseThrow(Supplierexception)
作用:如果optional不為空,則返回optional中的對象;如果為null,則拋出Supplier函數生成的異常
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 如果數據為null,拋出 指定異常
String name = userOptional.orElseThrow(() new RuntimeException("無數據")).getName();
System.out.println(name);
filter(Predicatep)
作用:如果optional不為空,則執行斷言函數p,如果p的結果為true,則返回原本的optional,否則返回空的optional
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 過濾名字長度大于3,如果有值才輸出,沒值就不輸出
userOptional.filter(item -> item.getName().length() > 3).ifPresent(System.out::println);
map(Function mapper)
作用:如果optional不為空,則將optional中的對象 t 映射成另外一個對象 u,并將 u 存放到一個新的optional容器中,該方法與Stream的map作用一樣
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 只獲取用戶名
String name = userOptional.map(User::getName).orElse("添甄");
System.out.println(name);
flatMap(Function< T,Optional> mapper)
作用:在optional不為空的情況下,將對象t映射成另外一個optional,17-flatMapmap接收的是U類型,而flatMap接收的是Optional<U>類型,返回也是需要放進Optional中
源碼:

代碼實現:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(null);
Optional<String> optional = userOptional.flatMap(item -> Optional.ofNullable(item.getName()));
String name = optional.orElse("添甄");
System.out.println(name);
錯誤示范
獲取用戶名:
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
// 判斷是否有值
if (userOptional.isPresent()) {
String name = userOptional.get().getName();
System.out.println(name);
}else {
System.out.println("無值");
}
通過調用isPresent方法判斷是否有值,這還是增加了判斷,破壞代碼結構
正確姿勢:
多用map,orElse,filter方法發揮Optional的作用
User user = new User(1L,"格雷福斯");
Optional<User> userOptional = Optional.ofNullable(user);
String name = userOptional.map(User::getName).orElse("無值");
System.out.println(name);
總結
- Optional是一個用來解決null值,避免發生空指針異常的容器,配合Lambda表達式寫出優雅代碼
- 靜態工廠方法Optional.empty()、Optional.of()以及Optional.ofNullable()創建Optional對象
- Optional類包含多種方法,其中map、flatMap、filter,它們在概念上與Stream類中對應的方法十分相似
- 使用Optional能幫助你開發出更便于閱讀和簡介的程序
- 多使用Optional中的方法給定默認值,比如map、orElse等方法來避免過多的if判斷
文章出自:??石添的編程哲學??,如有轉載本文請聯系【石添的編程哲學】今日頭條號。