我想在搬磚的過程中,大家一定都遇到過 NPE (NullPointerException) 吧。當(dāng)我們嘗試使用一個還沒有初始化或者初始化為 null 的變量時,就會出現(xiàn) NPE。Java 8 添加了 Optional
來嘗試解決這個問題。接下來讓我們來了解一下這個類吧操灿。
空(Null) 到底是什么類型 ?
在 Java 中锯仪,我們使用引用來訪問一個對象實(shí)例,當(dāng)這個引用不知道指向那個對象實(shí)例時趾盐,我們通常都將它設(shè)置為 null庶喜。
平時在搬磚過程中,null 的使用是那么的普遍,以致于我們很少會去考慮到它芬失。如果沒有特殊指定峻村,對象的實(shí)例變量通常都自動初始化為 null锚扎,當(dāng)我們不知道給這個引用賦什么值的時候芍秆,通常也會將 null 賦值給它荆虱。
那么 空(Null) 到底是什么類型呢骑脱?
僅供參考菜枷,在 Java 中,空(Null) 其實(shí)也是一種類型叁丧,只不過它很特殊犁跪,我們只能將 null 這個字面量賦值給這種類型椿息。而且這種類型不像其他的 Java 類型,null 可以賦值給任何其它的 Java 類型而不會出現(xiàn)錯誤坷衍。
返回 null 有什么問題嗎 ?
在開發(fā)過程中寝优,我們經(jīng)常會使用第三方提供的 API 接口轿腺,但是這些 API 接口的返回值可能為 null魄健,返回結(jié)果的說明可能在 API 文檔中已經(jīng)有說明,但是當(dāng)我們開發(fā)的時候润匙,由于各種原因迁杨,沒有仔細(xì)閱讀文檔(甚至就沒有讀過文檔)钻心,忘記了處理返回值為 null 的情況,那么這就可能會存在 NPE 問題铅协。而且這種現(xiàn)象在平時開發(fā)過程中是經(jīng)常發(fā)生的捷沸。
那么怎么有效的解決這個問題呢?一個比較好的辦法就是對返回值都進(jìn)行初始化狐史,但是初始化的值不能是 null(例子就是痒给,如果方法返回值是一個 List,那么我們可以返回一個 Collections.emptyList())骏全,這樣就不會出現(xiàn) NPE 了苍柏,你們說是不是這個道理?
嗯姜贡,理想很美好试吁,現(xiàn)實(shí)很骨感。在實(shí)際中楼咳,我們有可能找不到這么一個不是 null 的初始化值熄捍,那么該怎么辦呢?于是 Optional
出現(xiàn)了母怜。
Java 8 的 Optional 怎么解決 NPE 呢治唤?
Optional
就是一個引用為空的非空值。Optional
可能包含一個非空的引用糙申,也可能不會包含什么宾添,記住永遠(yuǎn)不要說 Optional 包含 null。
Optional<Integer> canBeEmpty1 = Optional.of(5);
canBeEmpty1.isPresent(); // returns true
canBeEmpty1.get(); // returns 5
Optional<Integer> canBeEmpty2 = Optional.empty();
canBeEmpty2.isPresent(); // returns false
我們可以把 Optional 當(dāng)做一個單值容器柜裸,它里面可能會裝一個值缕陕,也可能什么都不裝。
需要說明的是 Optional
類并不是替代 null 的疙挺,加入 Optional
類是為了可以設(shè)計出更容易理解的 API 接口扛邑。比如
public User getUserFromDB(long id);
我們可能調(diào)用這個接口后,直接就把 User 這個引用拿來使用了铐然,忘記判斷引用可能為空的情況蔬崩。那么如果這個接口是這樣的
public Optional<User> getUserFromDB(long id);
當(dāng)我們看到這個就接口的時候就會意識到恶座,這個接口返回的結(jié)果可能會為空,這樣我們就會先對這個值做判斷沥阳,然后再使用它跨琳。是不是一眼就明白了。
下面我們舉一些使用 Optional
的例子
a) 創(chuàng)建 Optional 對象
有 3 種方法來創(chuàng)建 Optional 對象桐罕。
i) 使用 Optional.empty() 創(chuàng)建沒有值的 Optional 容器脉让。
Optional<Integer> possible = Optional.empty();
ii) 使用 Optional.of() 創(chuàng)建一個裝有值的 Optional 容器。如果你傳一個 null 給 Optional.of() 方法功炮,那么會拋出 NullPointerException溅潜。
Optional<Integer> possible = Optional.of(5);
iii) 使用 Optional.ofNullable() 創(chuàng)建一個可能沒有值的 Optional 容器。
Optional<Integer> possible = Optional.ofNullable(null);
//or
Optional<Integer> possible = Optional.ofNullable(5);
b) Optional 容器有值處理
當(dāng)獲得了 Optional 對象薪伏,首先應(yīng)該檢查一下 Optional 容器中是否有值滚澜。
Optional<Integer> possible = Optional.of(5);
possible.ifPresent(System.out::println);
當(dāng)然,上面這段代碼也可以寫成下面?zhèn)鹘y(tǒng)的樣子
if(possible.isPresent()){
System.out.println(possible.get());
}
但是這并不是推薦的寫法嫁怀,要不然 Java 8 的 lambda 表達(dá)式有何用设捐?
c) Optional 容器無值處理
當(dāng)返回值不存在的時候,通常情況下我們會返回一個默認(rèn)值眶掌。一般我們會使用三元運(yùn)算符來處理挡育,但是使用 Optional 我們可以這樣
//Assume this value has returned from a method
Optional<Company> companyOptional = Optional.empty();
//Now check optional; if value is present then return it,
//else create a new Company object and retur it
Company company = companyOptional.orElse(new Company());
//OR you can throw an exception as well
Company company = companyOptional.orElseThrow(IllegalStateException::new);
d) 通過 filter 方法做過濾
通常我們回去一個結(jié)果需要對這個結(jié)果做一些過濾巴碗,篩選出符合我們要求的結(jié)果朴爬。
Optional<Company> companyOptional = Optional.empty();
companyOptional.filter(department -> "Finance".equals(department.getName())
.ifPresent(() -> System.out.println("Finance is present"));
filter 方法接受一個 predicate 參數(shù)。如果 Optional
有值而且滿足 predicate
橡淆,filter 方法就會返回這個值召噩,否則就會返回一個無值的 Optional。
哇哦逸爵,現(xiàn)在我們已經(jīng)不用在代碼中寫各式各樣的 null 值判斷邏輯了具滴,是不是看著更簡潔了呢。
Optional 內(nèi)部是怎么處理的呢?
看看 Optional.java
师倔,Optional 持有的值定義
/**
* If non-null, the value; if null, indicates no value is present
*/
private final T value;
如果值為空构韵,使用的是一個靜態(tài)變量表示
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
Optional
類的構(gòu)造函數(shù)都是私有的,這樣就確保只能使用上面提供的3種創(chuàng)建 Optional 實(shí)例趋艘。
當(dāng)創(chuàng)建一個有值的 Optional 實(shí)例時疲恢,會對傳入的 value 進(jìn)行判斷
this.value = Objects.requireNonNull(value);
當(dāng)從 Optional 獲取值得時候,如果 value 為 null 會拋出 NoSuchElementException 異常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
同樣的瓷胧,Optional
類中的其他方法都是圍繞著這個 value 進(jìn)行的显拳。
Opti0nal 嘗試解決的
Optional
類嘗試解決 Java 中 NPE 問題。通過 Optional
類設(shè)計出更容易理解的 API搓萧,如果 Optional
類從開始就被設(shè)計出來杂数,我相信在現(xiàn)在的類庫宛畦、應(yīng)用中對 null 值的處理會更加合理。
通過使用Optional
類揍移,這回強(qiáng)迫開發(fā)者思考為空的異常情況次和。當(dāng)編寫 API 的時候,如果返回值是Optional
類羊精,編寫 API 的開發(fā)者不得不考慮為空的這種情況斯够,因?yàn)椴荒苤苯臃祷?null 了,要不然編譯也不會通過的喧锦。
Opti0nal 不能解決的
正如前面說到的读规,Opti0nal
并不能代替所有 null 的情況。當(dāng)函數(shù)的入?yún)⒂?null 的時候燃少,并不推薦使用 Opti0nal
束亏。你想想當(dāng)你調(diào)用一個接口的時候,我們還得封裝一個成 Opti0nal
對象給它阵具,是不是有點(diǎn)麻煩碍遍。
為了更優(yōu)雅的使用 Opti0nal
類,下面情況最好不要使用
- 在數(shù)據(jù)模型層(Opti0nal 不可序列化)
- 在 DTO 中(Opti0nal 不可序列化)
- 在方法的入?yún)⒅?/li>
- 在構(gòu)造函數(shù)中
在什么地方使用 Opti0nal 呢阳液?
Opti0nal
應(yīng)該作為可能返回為空的方法的返回值類型怕敬。
下面這段引用時 OpenJDK 中的部分
The JSR-335 EG felt fairly strongly that Optional should not be on any more than needed to support the optional-return idiom only.
Someone suggested maybe even renaming it to "OptionalReturn".
這段話我覺得表達(dá)的就是 Opti0nal
應(yīng)該作為可能返回為空的方法的返回值類型吧。
總結(jié)
本文主要介紹了 java.util.Optional
的使用帘皿。 Opti0nal
類的目的并不是取代 null东跪,而是幫助設(shè)計出更好理解的 API 接口,通過方法返回值鹰溜,提醒開發(fā)者處理可能出現(xiàn) null 的異常情況虽填。