若是要給 Java 全部異常弄個榜單,我會選擇將 NullPointerException
放在榜首。這個異常潛伏在代碼中,就像個遙控炸彈,不知道何時這個按鈕會被忽然按下(傳入 null 對象)。html
還記得剛入行程序員的時候,三天兩頭碰到空指針異常引起的 Bug,解決完一個,又在另外一處碰到。那時候師兄就教我,不要相信任何『對象』,特別是別人給你的,這些地方都加上判斷。因而代碼一般爲會變成下面這樣:java
if(obj!=null){
// do something
}
複製代碼
有了這個防護以後,雖然不用再擔憂空指針異常,可是過多的判斷語句使得代碼變得臃腫。程序員
假設咱們存在以下對象關係安全
本來爲了獲取圖中的 name
屬性,本來一句代碼就能夠輕鬆完成。oracle
Staff staff=..;
staff.getDepartment().getCompany().getName();
複製代碼
可是很不幸,爲了代碼的安全性,咱們不得不加入空指針判斷代碼。ide
Staff staff=..;
if (staff != null) {
Department department = staff.getDepartment();
if (department != null) {
Company company = department.getCompany();
if (company != null) {
return company.getName();
}
}
}
return "Unknown";
複製代碼
當其中對象爲
null
時,能夠返回默認值,如上所示。也能夠直接拋出其餘異常快速失敗。學習
雖然上面代碼變得更加安全,可是過多嵌套 if 語句下降代碼總體可讀性,提升複雜度。idea
所幸 Java 8 引入引入一個新類 Java.util.Optional<T>
,依靠 Optional 類提供 API,咱們能夠寫出既安全又具備閱讀性的代碼。spa
還在使用 JDK 6 ?那你也別急着關閉這篇文章。能夠考慮使用 Guava Optional。不過須要注意的是,Guava Optional API 與 JDK 存在差別,如下以 JDK8 Optional 爲例。3d
Optional 本質是一個容器,須要咱們將對象實例傳入該容器中。Optional
的構造方法爲 private
,沒法直接使用 new 構建對象,只能使用 Optional
提供的靜態方法建立。
Optional
三個建立方法以下:
Optional.of(obj)
,若是對象爲 null,將會拋出 NPE。Optional.ofNullable(obj)
,若是對象爲 null,將會建立不包含值的 empty Optional
對象實例。Optional.empty()
等同於 Optional.ofNullable(null)
只有在肯定對象不會爲 null 的狀況使用 Optional#of
,不然建議使用 Optional#ofNullable
方法。
對象實例存入 Optional
容器中以後,最後咱們須要從中取出。Optional#get
方法用於取出內部對象實例,不過須要注意的是,若是是 empty Optional 實例,因爲容器內沒有任何對象實例,使用 get
方法將會拋出 NoSuchElementException
異常。
爲了防止異常拋出,可使用 Optional#isPresent
。這個方法將會判斷內部是否存在對象實例,若存在則返回 true。
示例代碼以下:
Optional<Company> optCompany = Optional.ofNullable(company);
// 與直接使用空指針判斷沒有任何區別
if (optCompany.isPresent()) {
System.out.println(optCompany.get().getName());
}
複製代碼
仔細對比,能夠發現上面用法與空指針檢查並沒有差異。剛接觸到 Optional
,看到不少文章介紹這個用法,那時候一直很疑惑,這個解決方案不是更加麻煩?
後來接觸到 Optional
其餘 API,我才發現這個類真正有意義是下面這些 API。若是使用過 Java8 Stream 的 API,下面 Optional
API 你將會很熟悉。
一般狀況下,空指針檢查以後,若是對象不爲空,將會進行下一步處理,好比打印該對象。
Company company = ...;
if(company!=null){
System.out.println(company);
}
複製代碼
上面代碼咱們可使用 Optional#ifPresent
代替,以下所示:
Optional<Company> optCompany = ...;
optCompany.ifPresent(System.out::println);
複製代碼
使用 ifPresent
方法,咱們不用再顯示的進行檢查,若是 Optional
爲空,上面例子將再也不輸出。
有時候咱們須要某些屬性知足必定條件,才進行下一步動做。這裏假設咱們當 Company name 屬性爲 Apple,打印輸出 ok。
if (company != null && "Apple".equals(company.getName())) {
System.out.println("ok");
}
複製代碼
下面使用 Optional#filter
結合 Optional#ifPresent
重寫上面的代碼,以下所示:
Optional<Company> companyOpt=...;
companyOpt
.filter(company -> "Apple".equals(company.getName()))
.ifPresent(company -> System.out.println("ok"));
複製代碼
filter
方法將會判斷對象是否符合條件。若是不符合條件,將會返回一個空的 Optional
。
當一個對象爲 null 時,業務上一般能夠設置一個默認值,從而使流程繼續下去。
String name = company != null ? company.getName() : "Unknown";
複製代碼
或者拋出一個內部異常,記錄失敗緣由,快速失敗。
if (company.getName() == null) {
throw new RuntimeException();
}
複製代碼
Optional
類提供兩個方法 orElse
與 orElseThrow
,能夠方便完成上面轉化。
// 設置默認值
String name=companyOpt.orElse(new Company("Unknown")).getName();
// 拋出異常
String name=companyOpt.orElseThrow(RuntimeException::new).getName();
複製代碼
若是 Optional
爲空,提供默認值或拋出異常。
熟悉 Java8 Stream 同窗的應該瞭解,Stream#map
方法能夠將當前對象轉化爲另一個對象, Optional#map
方法也與之相似。
Optional<Company> optCompany = ...;
Optional<String> nameopt = optCompany.map(Company::getName);
複製代碼
map 方法能夠將原先 Optional<Company>
轉變成 Optional<String>
,此時 Optional 內部對象變成 String 類型。若是轉化以前 Optional
對象爲空,則什麼也不會發生。
另外 Optional 還有一個 flatMap
方法,二者區別見下圖。
Department#getCompany
返回對象爲Optional<Company>
上面咱們學習了 Optional
類主要 API ,下面咱們使用 Optional
重構文章剛開頭的代碼。爲了方便讀者對比,將上面的代碼複製了下來。
代碼重構前:
if (staff != null) {
Department department = staff.getDepartment();
if (department != null) {
Company company = department.getCompany();
if (company != null) {
return company.getName();
}
}
}
return "Unknown";
複製代碼
首先咱們須要將 Staff
,Department
修改 getter 方法返回結果類型改爲 Optional
。
public class Staff {
private Department department;
public Optional<Department> getDepartment() {
return Optional.ofNullable(department);
}
...
}
public class Department {
private Company company;
public Optional<Company> getCompany() {
return Optional.ofNullable(company);
}
...
}
public class Company {
private String name;
public String getName() {
return name;
}
...
}
複製代碼
而後綜合使用 Optional API 重構代碼以下:
Optional<Staff> staffOpt =...;
staffOpt
.flatMap(Staff::getDepartment)
.flatMap(Department::getCompany)
.map(Company::getName)
.orElse("Unknown");
複製代碼
能夠看到重構以後代碼利用 Optional
的 Fluent Interface,以及 lambda 表達式,使代碼更加流暢連貫,而且提升代碼總體可讀性。
一、Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!
三、Optionals: Patterns and Good Practices
三、Java8 in Action
歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客:studyidea.cn