前言
Java 8中引入了 Optional
類來解決 NullPointerException
與繁瑣的 null
檢查,該類首次出現在 Guava
。Java 8 才成為類庫中的一部分。
入門
Optional
是一個封裝值的類,用於保存類型為 T
的值;本質上,Optional
就是一個容器。
舉例來說,一個人可能有車也可能沒有,那么 Person
類內部 car
變量就不應該聲明為 Car
,當變量存在時,Optional
類只是對 Car
的簡單封裝。變量不存在時,會使用 Optional.empty()
方法返回空的 Optional
對象。如下所示:
但是 null
引用和 Optional.empty()
有什么本質區別?從語義上,它們可以當成一回事兒,但實際上差別非常大:如果嘗試解引用一個 null
,一定會觸發 NullPointerException
,不過使用 Optional.empty()
是一個有效的對象。
下面我們來看一下 Optional
提供的功能。
創建
說到 Optional
的功能,我們首先要了解 Optional
實例的創建。
空Optional
正如前文提到,你可以通過靜態工廠方法 Optional.empty
,創建一個空的 Optional
對象:
Optional<Car> option = Optional.empty();
因為 empty()
本身代表的就是空對象,所以調用 get
方法會拋出 NoSuchElementException
異常。
非空Optional
你還可以使用靜態工廠方法 Optional.of
,依據一個非空值創建一個 Optional
對象:
Optional<Car> optional = Optional.of(car);
如果 car
是一個 null
,這段代碼會立即拋出一個 NullPointerException
,而不是等到你試圖訪問 car
的屬性值時才返回一個錯誤。
可為null的Optional
最后,使用靜態工廠方法 Optional.ofNullable
,你可以創建一個允許 null
值的 Optional
對象:
Optional<Car> optional = Optional.ofNullable(car);
如果 car
是 null
,那么得到的 Optional
對象就是個空對象。我們可以查看一下它的實現原理:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
根據它的實現方式,我們可知,傳入的值是空值時,會返回 Optional.empty()
空對象。這有利於我們封裝那些可能為 null
的值。例如,有一個 Map<String, Object>
實例,訪問 key
索引時,如果沒有與 key
關聯的值,則會返回一個 null
。因此,我們可以使用 Optional.ofNullable
方法封裝返回值:
Optional<Object> value = Optional.ofNullable(map.get("key"));
// 可以由上代碼代替 Object value = map.get("key");
這樣可以將潛在的 null
隱患替換為空的 Optional
對象。
操作
我們創建了 Optional
實例后,需要對該實例進行操作。
isPresent & get
在 Optional
類中,isPresent
方法對 Optional
實例進行判斷,是否包含值,如果存在值,就返回 true
,否則返回 false
;與之相對的是 isEmpty
方法Optional
類中還有 get
方法,它是用來獲取 Optional
實例中的值。
Optional<String> optional = Optional.of("is present");
if (optional.isPresent()) {
System.out.println("the value is " + optional.get());
}
isPresent
與 get
一般組合使用來避免 NullPointerException
:
public boolean isPresent() {
return value != null;
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
從源碼中可看出,get
方法在取值時,要進行判空操作,如果不使用 isPresent
方法,可能會出現空指針異常。但是這種方式和在代碼中if(null != value)
沒有區別,因此我們要盡量避免使用該組合。
ifPresent
除了 isPresent
的簡潔方法,Optional
還提供了接收函數式參數的接口 ifPresent
:
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
該方法會接收一個消費型函數。如果 Optional
實例中的值不為空,則調用 Consumer
的 accept
方法對 value
進行消費,若為空則不做處理。上面的例子可以使用 ifPresent
重寫:
Optional<String> optional = Optional.of("is present");
optional.isPresent((val) -> System.out.println("the value is " + val));
orElse
我們還可以使用 orElse
方法讀取 Optional
中的值。
public T orElse(T other) {
return value != null ? value : other;
}
使用這種方式可以定義一個默認值,這種方式當遭遇 Optional
中的變量為空時,默認值會作為該方法的返回值。
String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse("Default");
/**
* 輸出結果:
* Default
*/
orElseGet
如果該方法不夠,我們可以使用另一種方式 orElseGet
:
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
該方法與 orElse
的區別就是值不存在時,調用實現 Supplier
接口的方法或Lambda表達式來返回默認值。
String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse(() -> "Default");
/**
* 輸出結果:
* Default
*/
orElseThrow
orElseThrow
方法是在有值時返回其值,無值的時候會拋出由 Supplier
創建的異常。我們看一下它的實現原理:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
在 orElseThrow
原理中,會傳入一個Lambda表達式或方法,如果值不存在來拋出異常:
class NoValueException extends RuntimeException {
public NoValueException() {
super();
}
@Override
public String getMessage() {
return "No value present in the Optional instance";
}
}
public static Integer orElseThrow() {
return (Integer) Optional.empty().orElseThrow(NoValueException::new);
}
public static void main(String[] args) {
orElseThrow();
}
/**
* 控制台會拋出異常:
* Exception in thread "main" xx.NoValueException: No value present in the Optional instance
*/
orElseThrow
與 orElseGet
的區別就是一個在無值的時候拋出異常,一個在無值的時候使用Lambda表達式來實現默認值。
orElseThrow
只是在無值的時候拋出異常,那本身會拋出異常的方法呢?
現在,我們拿 Integer.parseInt(String)
做個例子:
public static Integer toInt(String s) {
try {
// 如果String能轉換為對應的Integer,將其封裝在Optional對象中返回
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return null; // 返回null 或者拋出異常
}
}
在將 String
轉換為 int
時,如果無法解析到對應的整型,該方法會拋出 NumberFormatException
異常。我們在該方法中使用 try/catch
語句捕獲了該異常,不能使用 if
條件判斷來控制一個變量的值是否為空。
這時,我們可以使用 Optional
類,來對無法轉換的 String
時返回的非法值進行建模,因此,我們可以對上述方法進行改進:
public static Optional<Integer> toInt(String s) {
try {
// 如果String能轉換為對應的Integer,將其封裝在Optional對象中返回
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty(); // 否則返回一個空的 Optional 對象
}
}
這種返回 Optional
的方式適用很多方法,我們只需要獲取被 Optional
包裝的值的實例即可。
map
Optional
提供了 map
方法用於從對象中提取信息,它的工作原理如下:
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));
}
}
map
操作會將提供的函數應用於流的每個元素。我們可以把 Optional
對象看成一種特殊的集合數據,它至多包含一個元素。如果 Optional
包含一個值,那通過實現了 Function
接口的 Lambda 表達式對值進行轉換。如果不熟悉 Function
接口,可以參考這篇文章。map
方法示例如下:
class Car {
private String name;
private String type;
...省略getter與setter...
}
Optional<Car> optional = Optional.ofNullable(car);
Optional<String> name = optional.map(Car::getName);
flatMap
我們可以使用 map
方法來從被 Optional
類包裝的 Person
類中獲取 Car
的名稱:
class Person {
private Optional<Car> car;
public Person(Car car) {
this.car = Optional.of(car);
}
...省略getter與setter...
}
Person person = new Person(new Car());
Optional<Person> optPerson = Optional.of(person);
Optional<String> name = optPerson.map(Person::getCar)
.map(Car::getName);
不幸的是,這段代碼無法通過編譯。為什么呢?optPerson
是 Optional<Person>
類型的變量,調用 map
方法應該沒有問題。但 getCar
返回的是一個 Optional<Car>
類型的對象,這意味着 map
操作的結果是一個 Optional<Optional<Car>>
類型的對象。因此,它對 getName
的調用是非法的,因為最外層的 optional
對象包含了另一個 optional
對象的值,而它當然不會支持 getName
方法。
所以,我們使用 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);
}
}
參照 map
函數,使用 flatMap
重寫上述的示例:
Optional<String> name = optPerson.flatMap(Person::getCar).map(Car::getName);
filter
有時候我們需要對 Optional
中的值進行過濾,獲得我們需要的結果,我們就可以使用 filter
方法:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
該方法接受 Predicate
謂詞作為參數。如果 Optional
對象的值存在,並且符合謂詞的條件,即操作結果為true
,filter
方法不做任何改變並返回其值;否則就將該值過濾掉並返回一個空的 Optional
對象。
Optional<String> optionalS = Optional.of("13846901234");
optionalS = optionalS.filter(s -> s.contains("138"));
/**
* 上述 `filter` 方法滿足條件可以返回同一個Optional,否則返回空Optional
*/
總結
Java 8引入的 java.util.Optional<T>
讓我們以函數式編程的方式處理 null
,防止空指針異常;並支持多種方式用於操作值,比如:map
、flatMap
和 filter
,這樣可以拋棄嵌套的 if-else
代碼塊,設計更好的 API,代碼的可讀性也大大提高,但是如果在域模型中使用 Optional
,由於沒有實現 Serializable
接口,不能進行實例化,也不能作為類的字段。
更多內容請關注公眾號「海人為記」,回復「資源」即可獲得免費學習資源!