前言
在 Java 語言開發中,可能大多數程式設計師遇到最多的異常就是 NullPointException 空指標異常了。這個當初語言的開發者“僅僅因為這樣實現起來更容易”而允許空引用所帶來的代價是非常慘痛的。而我們開發者不得不使用多重 if 巢狀判斷來規避 NPE 或者通過多個 if 結合 return 語句來終止程式。且看一個例子
假如需要處理下面的巢狀物件,這是一個用於汽車、汽車保險的客戶。
public class Person {
private Car car;
public Car getCar() {
return car;
}
}
public class Car {
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
那麼下面的程式碼會存在怎樣的問題呢?
public String getCarInsuranceNames(Person person) {
return person.getCar().getInsurance().getName();
}
沒錯,當這個人沒有車 / 他的車沒有上保險時,程式碼會丟擲 NPE。或者說這個人根本就是 null,也會直接丟擲異常。我們常見的作法就是在每次 get 方法之後,進行 if 判斷,增加程式碼的健壯性。可是這樣程式碼會顯得十分臃腫。Java 語言的開發者們也在關注著這些問題。因此在 Java8 提供了新的 API:java.util.Optional 用來優雅的處理 null。接下來就請讀者和我一起揭開 Optional 神祕的面紗吧!
PS:Optional 類提供的很多 API 結合 Lambda 表示式食用更佳,另外還有很多 API 和 Stream 流中同名 API 的思想基本一致。因此建議讀者先行了解這兩個知識點,可以在我的部落格 Java8新特性 標籤下學習
宣告:本文首發於部落格園,作者:後青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!
Optional 入門
Optional
現在我們嘗試著重構之前關於 人 車 保險 的程式碼
public class Person {
private Optional<Car> car;
public Optional<Car> getCar() {
return car;
}
}
public class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
注意:對於保險來說,我們從邏輯層面限定每個保險公司都有名稱,如果沒有,那一般是資料出了問題而非程式碼的問題,開發者應該著手去尋找為什麼資料庫存在名字為空的保險公司。而不是這裡丟擲 NPE,故而我們不用將 Insurance 的 name 欄位使用 Optional 包裹
通過上面的程式碼,我們已經將物件由 Optional 所包裹了,那接下來我們該如何使用它呢?
建立 Optional 物件
建立一個空物件
Optional<Object> empty = Optional.empty();
Optional.empty(); 該方法返回一個空物件,
根據一個非空值建立
Optional<Car> car = Optional.of(c);
Optional.of(T t); 方法會返回一個 Optional
允許空值建立
Optional
為了避免在建立 Optional 物件時,由於源物件為空而引發的 NPE,該類還提供了 ofNullable 方法,當引數為 null 時,返回 Optional.empty()。內部的 API 是這樣的
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
map --- 從 Optional 中提取和轉換值
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));
}
}
Optional 類提供 map 方法,接收一個函式式介面 Function 的實現類,如果呼叫者是空的,則返回 empty(),否則對 Optional 中的物件 value 呼叫 Function 實現類中的 apply() 方法,再包裝成 Optional 返回。可以用下面的圖直觀的看到 map 執行的過程:
請注意,在 map 執行完 apply 方法拿到返回值之後,會主動將返回值再次包裹成 Optional 物件。因此我們如果按照下面的方式改造我們之前的方法,編譯是無法通過的:
person.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName);
我們來分析一下: person.map(Person::getCar) 改造後的 person 類中, getCar 方法返回 Optional
幸運的是,和 Stream 一樣,Optional 也提供了扁平化流的方法 flatMap()。且看原始碼
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}
flatMap() 比 map() 方法多了一個執行完後將巢狀 Optional 強轉成 Optional 的操作,避免了流不能繼續使用的尷尬處境。因此,我們可以將獲取保險公司名稱的方法改造成下面這樣:
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
其中 orElse() 方法表示當最終 Optional 包裹的物件還是空時,返回的預設值
PS:由於 Optional 並沒有實現序列化介面,因此如果你的專案中使用了某些要求序列化的框架,並且在某個類中使用 Optional 包裹了欄位。可能會由序列化引發程式故障。
操作 Optional 中的變數
get()
通過 get() 方法獲取變數,如果變數存在就直接得到該變數,否則丟擲一個 throw new NoSuchElementException("No value present"); 異常。一般不建議使用該方法畢竟直接用 get() 方法了,還要整 Optional 這些花裡胡哨的幹啥呢
orElse()
在物件為 null 時提供一個預設值
orElseGet(Supplier<? extends T> other)
在物件為 null 通過呼叫 supplier 提供者介面的實現,返回一個值
orElseThrow()
在物件為 null 丟擲一個可定製的異常資訊,可以用來丟擲專案中的自定義異常,以便全域性異常捕獲器抓取及響應資料
ifPresent(Consumer<? super T> action)
當物件不為 null 時,執行消費者操作。為 null 時啥也不幹
更優雅的判斷語句
我們常常呼叫某個物件的某個方法去判斷其屬性。為了安全操作。首先需要對該物件進行非空校驗。例如要檢查保險公司名稱是否為 Keats,需要這麼寫
if(i != null && "Keats".equals(i.getName())){
System.out.println("yes");
}
現在我們可以這麼寫
Optional<Insurance> insurance = Optional.ofNullable(i);
insurance.filter(in -> "Keats".equals(in.getName())).ifPresent(in -> System.out.println("yes"));
先看 filter 的原始碼
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
首先第一步檢查了謂詞實現非空,第二步判斷 Optional 中的物件如果為空則返回空 Optional,如果不為空執行謂詞方法,條件成立則返回該物件。否則返回空 Optional。即僅當 Optional 中物件不為 null 且符合條件時,返回該物件之後通過 ifPresent() 方法執行接下來的邏輯。非常方便易懂
其他
Optional 還提供了一些基礎型別物件對應的類,如 OptionalInt、OptionalLong 同 Stream 流一樣,採用基本操作型別處理資料,避免了自動拆裝箱帶來的效能損失。但卻犧牲了 map、flatMap、filter 方法。開發中需酌情使用
碼字不易,如果你覺得讀完以後有收穫,不妨點個推薦讓更多的人看到吧!