還在重復(fù)寫空指針檢查代碼?趕緊使用Optional吧!
作者:xcbeyond
瘋狂源自夢(mèng)想,技術(shù)成就輝煌!微信公眾號(hào):《程序猿技術(shù)大咖》號(hào)主,專注后端開發(fā)多年,擁有豐富的研發(fā)經(jīng)驗(yàn),樂于技術(shù)輸出、分享,現(xiàn)階段從事微服務(wù)架構(gòu)項(xiàng)目的研發(fā)工作,涉及架構(gòu)設(shè)計(jì)、技術(shù)選型、業(yè)務(wù)研發(fā)等工作。對(duì)于Java、微服務(wù)、數(shù)據(jù)庫、Docker有深入了解,并有大量的調(diào)優(yōu)經(jīng)驗(yàn)。
1、前言
作為一名Java程序員,無論是初入茅廬的菜鳥,還是久經(jīng)江湖的高手,曾經(jīng)肯定遭遇過各種各樣的異常錯(cuò)誤。在國外的一篇文章中,就統(tǒng)計(jì)了關(guān)于異常類型的排行榜,如下圖:
是的,你沒有看錯(cuò),NullPointerException位居榜首。
Null Reference的發(fā)明者Charles Antony Richard Hoare說過:“我稱之為我的十億美元錯(cuò)誤。這是1965年發(fā)明空引用的結(jié)果……這導(dǎo)致了無數(shù)的錯(cuò)誤,漏洞和系統(tǒng)崩潰,在最近40年中可能造成十億美元的痛苦和破壞。”
這看起來有些夸張,但毫無爭議的是NullPointerException簡直就是程序員心中的痛,并不是說它有多難以解決,而是為了解決它我們需要再付出了額外代價(jià)。
還記得當(dāng)初剛?cè)胄袝r(shí)候的你,三天兩頭碰到NullPointerException而引發(fā)的bug,解決完一個(gè),又在另一個(gè)地方遇到。這也慢慢讓你懂得,不要相信任何“對(duì)象”,特別是別人提供給你的,在使用的地方都加上判斷,這樣就放心多了。于是代碼通常就變成了下面這樣:
String name = "Unknown";
if (null != people) {
if (null != people.getName()) {
name = people.getName();
}
}
return name;
這樣處理,雖然不用擔(dān)心NullPointerException了,但是過多的判斷語句著實(shí)讓人頭皮發(fā)麻,代碼變得臃腫不堪。如果對(duì)象過于復(fù)雜,對(duì)象里面還有對(duì)象等等,你還要繼續(xù)逐層判斷么?
令人興奮的是,JDK1.8引入了一個(gè)新類java.util.Optional<T>,憑借Optional類提供的API,我們?cè)僖膊挥脫?dān)心NullPointerException了,更不會(huì)再去寫那些煩人的判斷啦。
2、Optional類
舉例來說,使用新類意味著,如果你知道一個(gè)人可能有也可能沒有車,那么Person類內(nèi)部的car變量就不應(yīng)該聲明為Car,遭遇某人沒有車時(shí)把null引用賦值給它,而是應(yīng)該像下圖這樣直接將其聲明為Optional類型。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-tsaZpPjQ-1597913478274)(./7.Optional示例圖.png)]
變量存在時(shí),Optional類只是對(duì)類簡單封裝。變量不存在時(shí),缺失的值會(huì)被建模成一個(gè)“空”的Optional對(duì)象,由方法Optional.empty()返回。Optional.empty()方法是一個(gè)靜態(tài)工廠方法,它返回Optional類的特定單一實(shí)例。
Optional,本質(zhì)上是一個(gè)容器對(duì)象,擁有一個(gè)非空值或空值,需要我們將對(duì)象實(shí)例傳入該容器中。如果值存在,Optional.isPresent()方法返回true,并通過Optional.get()方法獲取值。
Optional的構(gòu)造方法為private,無法直接使用new來創(chuàng)建Optional對(duì)象,只能使用Optional提供的靜態(tài)方法創(chuàng)建。
Optional提供的創(chuàng)建方法如下:
Optional.of(obj):如果對(duì)象為 null,將會(huì)拋出NullPointerException。
Optional.ofNullable(obj):如果對(duì)象為 null,將會(huì)創(chuàng)建不包含值的 EMPTY Optional對(duì)象實(shí)例(new Optional<>())。
Optional.empty() :等同于 Optional.ofNullable(null)。
其中,源碼片段如下:
/**
* Constructs an instance with the value present.
*
* @param value the non-null value to be present
* @throws NullPointerException if value is null
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
……
/**
* Returns an {@code Optional} with the specified present non-null value.
*
* @param <T> the class of the value
* @param value the value to be present, which must be non-null
* @return an {@code Optional} with the value present
* @throws NullPointerException if value is null
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
/**
* Returns an {@code Optional} describing the specified value, if non-null,
* otherwise returns an empty {@code Optional}.
*
* @param <T> the class of the value
* @param value the possibly-null value to describe
* @return an {@code Optional} with a present value if the specified value
* is non-null, otherwise an empty {@code Optional}
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
……
/**
* Returns an empty {@code Optional} instance. No value is present for this
* Optional.
*
* @apiNote Though it may be tempting to do so, avoid testing if an object
* is empty by comparing with {@code ==} against instances returned by
* {@code Option.empty()}. There is no guarantee that it is a singleton.
* Instead, use {@link #isPresent()}.
*
* @param <T> Type of the non-existent value
* @return an empty {@code Optional}
*/
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
強(qiáng)烈建議使用Optional.ofNullable(obj)方法,來創(chuàng)建Optional對(duì)象,并獲取對(duì)應(yīng)值。
3、Optional的使用
到目前為止,你已經(jīng)知道Optional的好處了吧,但是,我們?cè)撊绾问褂媚?
使用可接受null的Optional對(duì)象,即:使用靜態(tài)工程方法Optional.ofNullable(obj),創(chuàng)建一個(gè)可以允許null值的Optional對(duì)象:
Optional<People> optional = Optional.ofNullable(people);
即使people是null,optional對(duì)象也就是個(gè)空對(duì)象。
如果people不為null,根據(jù)Optional.isPresent()方法返回true,并通過Optional.get()方法獲取值。
為了避免NPE,Optional.isPresent()方法已經(jīng)對(duì)null進(jìn)行了判斷,若存在返回true。
People p = null;
if (optional.isPresent()) {
p = optional.get();
}
看到這里,你可能會(huì)發(fā)現(xiàn)這與null判斷檢查并無差異。
后來接觸到Optional其他API,我才發(fā)現(xiàn)真正體現(xiàn)它價(jià)值的是下面這些API。
3.1 Optional.map
從對(duì)象中獲取某個(gè)屬性,是最常見的操作。比如,你可能需要從people對(duì)象中獲取人名。在獲取人名之前,你需要檢查people對(duì)象是否為null,如下所示:
String name = null;
if (null != people) {
name = people.getName();
}
使用Optional.map方法,可以這么寫:
Optional<People> optional = Optional.ofNullable(people);
Optional<String> stringOptional = optional.map(People::getName);
3.2 Optional.orElse
當(dāng)一個(gè)對(duì)象為 null 時(shí),業(yè)務(wù)上通常可以設(shè)置一個(gè)默認(rèn)值,從而使流程繼續(xù)下去。
String name = null != people ? people.getName() : "Unknown";
或者拋出一個(gè)異常。
if (null != people.getName()) {
throw new RuntimeException();
}
Optional 類提供兩個(gè)方法 orElse 與 orElseThrow ,可以方便完成上面轉(zhuǎn)化。
// 設(shè)置默認(rèn)值
String name = optional.orElse(new People("Unknown")).getName();
// 拋出異常
String name = optional.orElseThrow(RuntimeException::new).getName();
如果 optional 為空,提供默認(rèn)值或拋出異常。
3.3 Optional.filter
你經(jīng)常需要調(diào)用某個(gè)對(duì)象的方法,查看它的某些屬性。比如,你可能需要檢查人名是否為“xcbeyond”。為了以一種安全的方式進(jìn)行這些操作,你首先需要判斷people對(duì)象是否為null,再調(diào)用它的方法getName,如下所示:
if (null != people && "xcbeyond".equals(people.getName())) {
System.out.println("ok");
}
使用Optional類提供的方法filter,可以很好的重構(gòu):
optional.filter(people1 -> "xcbeyond".equals(people.getName()))
.ifPresent(x -> System.out.print("ok"));
4、Optional重構(gòu)代碼
讓我們一起再看看文章開頭的代碼:
String name = "Unknown";
if (null != people) {
if (null != people.getName()) {
name = people.getName();
}
}
return name;
在知道了Optional之后,進(jìn)行代碼重構(gòu):
Optional<People> optional = Optional.ofNullable(people);
return optional.map(People::getName).orElse("Unknown");
結(jié)合Optional、Lambda表達(dá)式,可以明顯看到重構(gòu)之后,使得代碼更加流暢連貫,并且提高代碼整體可讀性。
參考文章:
1.https://dzone.com/articles/the-top-10-exception-types-in-production-java-appl
2.《Java 8實(shí)戰(zhàn)》