點擊下方“IT牧場”,選擇“設爲星標”
作者 | ES_her0
很多公衆號其實都發過 Optional 的文章, 但大多文章都是介紹了 Optional 的 API 用法,卻沒有給出怎麼正確的使用 Optional,這可能會誤導一部分小白使用者,私以爲,在項目中一知半解的使用 Optional,我更願意看到老老實實的 null 判斷。今天我給大家分享的這篇文章,便是 Java Optional 的一些 Best Practise 和一些反面的 Bad Practice,以供大家參考。
來自作者的說明
首先我們來看一下Optional
的作者 Brian Goetz 對這個 API 的說明:
Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using
null
for such was overwhelmingly likely to cause errors.
大意爲,爲了避免null
帶來的錯誤,我們提供了一個可以明確表示空值的有限的機制。
基礎理解
首先,Optional
是一個容器,用於放置可能爲空的值,它可以合理而優雅的處理null
。衆所周知,null
在編程歷史上極具話題性,號稱是計算機歷史上最嚴重的錯誤,感興趣可以讀一下這篇文章:THE WORST MISTAKE OF COMPUTER SCIENCE,這裏暫且不做過多討論。在 Java 1.8 之前的版本,沒有可以用於表示null
官方 API,如果你足夠的謹慎,你可能需要常常在代碼中做如下的判斷:
if (null != user) {
//doing something
}
if (StringUtil.isEmpty(string)) {
//doing something
}
確實,返回值是null
的情況太多了,一不小心,就會產生 NPE,接踵而來的就是應用運行終止,產品抱怨,用戶投訴。
1.8 之後,jdk 新增了Optional
來表示空結果。其實本質上什麼也沒變,只是增加了一個表達方式。Optional
表示空的靜態方法爲Optional.empty()
,跟null
有什麼本質區別嗎?其實沒有。翻看它的實現,Optional
中的 value 就是null
,只不過包了一層Optional
,所以說它其實是個容器。用之後的代碼可能長這樣:
// 1
Optional<User> optionalUser = RemoteService.getUser();
if (!optionalUser.isPresent()) {
//doing something
}
User user = optionalUser.get();
// 2
User user = optionalUser.get().orElse(new User());
看起來,好像比之前好了一些,至少看起來沒那麼笨。但如果採用寫法 1,好像更囉嗦了。
如果你對 kotlin 稍有了解,kotlin 的非空類型是他們大肆宣傳的"賣點"之一,通過var param!!
在使用它的地方做強制的空檢查,否則無法通過編譯,最大程度上減少了 NPE。其實在我看來,Optional
的方式更加優雅和靈活。同時,Optional
也可能會帶來一些誤解。
下面先說一些在我看來不合適的使用方式:
Bad Practice
1. 直接使用 isPresent() 進行 if 檢查
這個直接參考上面的例子,用if
判斷和 1.8 之前的寫法並沒有什麼區別,反而返回值包了一層Optional
,增加了代碼的複雜性,沒有帶來任何實質的收益。其實isPresent()
一般用於流處理的結尾,用於判斷是否符合條件。
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
2. 在方法參數中使用 Optional
我們用一個東西之前得想明白,這東西是爲解決什麼問題而誕生的。Optional
直白一點說就是爲了表達可空性,如果方法參數可以爲空,爲何不重載呢?包括使用構造函數也一樣。重載的業務表達更加清晰直觀。
//don't write method like this
public void getUser(long uid,Optional<Type> userType);
//use Overload
public void getUser(long uid) {
getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
//doing something
}
3. 直接使用 Optional.get
Optional
不會幫你做任何的空判斷或者異常處理,如果直接在代碼中使用Optional.get()
和不做任何空判斷一樣,十分危險。這種可能會出現在那種所謂的着急上線,着急交付,對Optional
也不是很熟悉,直接就用了。這裏多說一句,可能有人會反問了:甲方/業務着急,需求又多,哪有時間給他去做優化啊?因爲我在現實工作中遇到過,但這兩者並不矛盾,因爲代碼行數上差別並不大,只要自己平時保持學習,都是信手拈來的東西。
4. 使用在 POJO 中
估計很少有人這麼用:
public class User {
private int age;
private String name;
private Optional<String> address;
}
這樣的寫法將會給序列化帶來麻煩,Optional
本身並沒有實現序列化,現有的 JSON 序列化框架也沒有對此提供支持的。
5. 使用在注入的屬性中
這種寫法估計用的人會更少,但不排除有腦洞的。
public class CommonService {
private Optional<UserService> userService;
public User getUser(String name) {
return userService.ifPresent(u -> u.findByName(name));
}
}
首先依賴注入大多在 spring 的框架之下,直接使用@Autowired
很方便。但如果使用以上的寫法,如果userService
set 失敗了,程序就應該終止並報異常,並不是無聲無息,讓其看起來什麼問題都沒有。
Best and Pragmatic Practice
API
在說最佳實踐前,讓我們來看一下Optional
都提供了哪些常用 API。
1. empty()
返回一個Optional
容器對象,而不是 null。建議常用⭐⭐⭐⭐
2. of(T value)
創建一個Optional
對象,如果 value 是 null,則拋出 NPE。不建議用⭐⭐
3. ofNullable(T value)
同上,創建一個Optional
對象,但 value 爲空時返回Optional.empty()
。推薦使用⭐⭐⭐⭐⭐
4. get()
返回Optional
中包裝的值,在判空之前,千萬不要直接使用!儘量別用!⭐
5. orElse(T other)
同樣是返回Optional
中包裝的值,但不同的是當取不到值時,返回你指定的 default。看似很好,但不建議用⭐⭐
6. orElseGet(Supplier<? extends T> other)
同樣是返回Optional
中包裝的值,取不到值時,返回你指定的 default。看似和 5 一樣,但推薦使用⭐⭐⭐⭐⭐
7. orElseThrow(Supplier<? extends X> exceptionSupplier)
返回Optional
中包裝的值,取不到值時拋出指定的異常。阻塞性業務場景推薦使用⭐⭐⭐⭐
8. isPresent()
判斷Optional
中是否有值,返回 boolean,某些情況下很有用,但儘量不要用在 if 判斷體中。可以用⭐⭐⭐
9. ifPresent(Consumer<? super T> consumer)
判斷Optional
中是否有值,有值則執行 consumer,否則什麼都不幹。日常情況下請使用這個⭐⭐⭐⭐
TIPS
首先是一些基本原則:
-
不要聲明任何 Optional
實例屬性 -
不要在任何 setter 或者構造方法中使用 Optional
-
Optional
屬於返回類型,在業務返回值或者遠程調用中使用
1. 業務上需要空值時,不要直接返回 null,使用Optional.empty()
public Optional<User> getUser(String name) {
if (StringUtil.isNotEmpty(name)) {
return RemoteService.getUser(name);
}
return Optional.empty();
}
2. 使用 orElseGet()
獲取 value 有三種方式:get()
orElse()
orElseGet()
。這裏推薦在需要用到的地方只用 orElseGet()
。
首先,get()
不能直接使用,需要結合判空使用。這和!=null
其實沒多大區別,只是在表達和抽象上有所改善。
其次,爲什麼不推薦orElse()
呢?因爲orElse()
無論如何都會執行括號中的內容, orElseGet()
只在主體 value 是空時執行,下面看個例子:
public String getName() {
System.out.print("method called");
}
String name1 = Optional.of("String").orElse(getName()); //output: method called
String name2 = Optional.of("String").orElseGet(() -> getName()); //output:
如果上面的例子getName()
方法是一個遠程調用,或者涉及大量的文件 IO,代價可想而知。
但 orElse()
就一無是處嗎?並不是。orElseGet()
需要構建一個Supplier
,如果只是簡單的返回一個靜態資源、字符串等等,直接返回靜態資源即可。
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse(USER_STATUS);
}
//不要這麼寫
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse("UNKNOWN");//這樣每次都會新建一個String對象
}
3. 使用 orElseThrow()
這個針對阻塞性的業務場景比較合適,例如沒有從上游獲取到用戶信息,下面的所有操作都無法進行,那此時就應該拋出異常。正常的寫法是先判空,再手動 throw 異常,現在可以集成爲一行:
public String findUser(long id) {
Optional<User> user = remoteService.getUserById(id) ;
return user.orElseThrow(IllegalStateException::new);
}
4. 不爲空則執行時,使用 ifPresent()
這點沒有性能上的優勢,但可以使代碼更簡潔:
//之前是這樣的
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
//現在
status.ifPresent(System.out::println);
5. 不要濫用
有些簡單明瞭的方法,完全沒必要增加Optional
來增加複雜性。
public String fetchStatus() {
String status = getStatus() ;
return Optional.ofNullable(status).orElse("PENDING");
}
//判斷一個簡單的狀態而已
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
首先,null 可以作爲集合的元素之一,它並不是非法的;其次,集合類型本身已經具備了完整的空表達,再去包裝一層Optional
也是徒增複雜,收益甚微。例如,map 已經有了getOrDefault()
這樣的類似orElse()
的 API 了。
總結
Optional
的出現使 Java 對 null 的表達能力更近了一步,好馬配好鞍,合理使用可以避免大量的 NPE,節省大量的人力物力。以上內容也是本人查詢了很多資料,邊學邊寫的產出,如有錯漏之處,還請不吝指教。
乾貨分享
最近將個人學習筆記整理成冊,使用PDF分享。關注我,回覆如下代碼,即可獲得百度盤地址,無套路領取!
•001:《Java併發與高併發解決方案》學習筆記;•002:《深入JVM內核——原理、診斷與優化》學習筆記;•003:《Java面試寶典》•004:《Docker開源書》•005:《Kubernetes開源書》•006:《DDD速成(領域驅動設計速成)》•007:全部•008:加技術羣討論
加個關注不迷路
喜歡就點個"在看"唄^_^
本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。