Java8 Optional 最佳實踐!
作者:ES_her0
來源:xie.infoq.cn/article/e3d1f0f4f095397c44812a5be
很多公眾號其實都發(fā)過 Optional 的文章, 但大多文章都是介紹了 Optional 的 API 用法,卻沒有給出怎么正確的使用 Optional,這可能會誤導(dǎo)一部分小白使用者,私以為,在項目中一知半解的使用 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
nullfor such was overwhelmingly likely to cause errors.
大意為,為了避免null帶來的錯誤,我們提供了一個可以明確表示空值的有限的機制。
基礎(chǔ)理解
首先,Optional是一個容器,用于放置可能為空的值,它可以合理而優(yōu)雅的處理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的情況太多了,一不小心,就會產(chǎn)生 NPE,接踵而來的就是應(yīng)用運行終止,產(chǎn)品抱怨,用戶投訴。
1.8 之后,jdk 新增了Optional來表示空結(jié)果。其實本質(zhì)上什么也沒變,只是增加了一個表達方式。Optional表示空的靜態(tài)方法為Optional.empty(),跟null有什么本質(zhì)區(qū)別嗎?其實沒有。翻看它的實現(xiàn),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的方式更加優(yōu)雅和靈活。同時,Optional也可能會帶來一些誤解。
下面先說一些在我看來不合適的使用方式:
Bad Practice
1. 直接使用 isPresent() 進行 if 檢查
這個直接參考上面的例子,用if判斷和 1.8 之前的寫法并沒有什么區(qū)別,反而返回值包了一層Optional,增加了代碼的復(fù)雜性,沒有帶來任何實質(zhì)的收益。其實isPresent()一般用于流處理的結(jié)尾,用于判斷是否符合條件。
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
2. 在方法參數(shù)中使用 Optional
我們用一個東西之前得想明白,這東西是為解決什么問題而誕生的。Optional直白一點說就是為了表達可空性,如果方法參數(shù)可以為空,為何不重載呢?包括使用構(gòu)造函數(shù)也一樣。重載的業(yè)務(wù)表達更加清晰直觀。
//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()和不做任何空判斷一樣,十分危險。這種可能會出現(xiàn)在那種所謂的著急上線,著急交付,對Optional也不是很熟悉,直接就用了。這里多說一句,可能有人會反問了:甲方/業(yè)務(wù)著急,需求又多,哪有時間給他去做優(yōu)化???因為我在現(xiàn)實工作中遇到過,但這兩者并不矛盾,因為代碼行數(shù)上差別并不大,只要自己平時保持學習,都是信手拈來的東西。
4. 使用在 POJO 中
估計很少有人這么用:
public class User {
private int age;
private String name;
private Optional<String> address;
}
這樣的寫法將會給序列化帶來麻煩,Optional本身并沒有實現(xiàn)序列化,現(xiàn)有的 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 失敗了,程序就應(yīng)該終止并報異常,并不是無聲無息,讓其看起來什么問題都沒有。
Best and Pragmatic Practice
API
在說最佳實踐前,讓我們來看一下Optional都提供了哪些常用 API。
1. empty()
返回一個Optional容器對象,而不是 null。建議常用????
2. of(T value)
創(chuàng)建一個Optional對象,如果 value 是 null,則拋出 NPE。不建議用??
3. ofNullable(T value)
同上,創(chuàng)建一個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中包裝的值,取不到值時拋出指定的異常。阻塞性業(yè)務(wù)場景推薦使用????
8. isPresent()
判斷Optional中是否有值,返回 boolean,某些情況下很有用,但盡量不要用在 if 判斷體中。可以用???
9. ifPresent(Consumer<? super T> consumer)
判斷Optional中是否有值,有值則執(zhí)行 consumer,否則什么都不干。日常情況下請使用這個????
TIPS
首先是一些基本原則:
不要聲明任何 Optional實例屬性不要在任何 setter 或者構(gòu)造方法中使用 OptionalOptional屬于返回類型,在業(yè)務(wù)返回值或者遠程調(diào)用中使用
1. 業(yè)務(wù)上需要空值時,不要直接返回 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()不能直接使用,需要結(jié)合判空使用。這和!=null其實沒多大區(qū)別,只是在表達和抽象上有所改善。
其次,為什么不推薦orElse()呢?因為orElse()無論如何都會執(zhí)行括號中的內(nèi)容, orElseGet()只在主體 value 是空時執(zhí)行,下面看個例子:
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()方法是一個遠程調(diào)用,或者涉及大量的文件 IO,代價可想而知。
但 orElse()就一無是處嗎?并不是。orElseGet()需要構(gòu)建一個Supplier,如果只是簡單的返回一個靜態(tài)資源、字符串等等,直接返回靜態(tài)資源即可。
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()
這個針對阻塞性的業(yè)務(wù)場景比較合適,例如沒有從上游獲取到用戶信息,下面的所有操作都無法進行,那此時就應(yīng)該拋出異常。正常的寫法是先判空,再手動 throw 異常,現(xiàn)在可以集成為一行:
public String findUser(long id) {
Optional<User> user = remoteService.getUserById(id) ;
return user.orElseThrow(IllegalStateException::new);
}
4. 不為空則執(zhí)行時,使用 ifPresent()
這點沒有性能上的優(yōu)勢,但可以使代碼更簡潔:
//之前是這樣的
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
//現(xiàn)在
status.ifPresent(System.out::println);
5. 不要濫用
有些簡單明了的方法,完全沒必要增加Optional來增加復(fù)雜性。
public String fetchStatus() {
String status = getStatus() ;
return Optional.ofNullable(status).orElse("PENDING");
}
//判斷一個簡單的狀態(tài)而已
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
首先,null 可以作為集合的元素之一,它并不是非法的;其次,集合類型本身已經(jīng)具備了完整的空表達,再去包裝一層Optional也是徒增復(fù)雜,收益甚微。例如,map 已經(jīng)有了getOrDefault()這樣的類似orElse()的 API 了。
總結(jié)
Optional的出現(xiàn)使 Java 對 null 的表達能力更近了一步,好馬配好鞍,合理使用可以避免大量的 NPE,節(jié)省大量的人力物力。以上內(nèi)容也是本人查詢了很多資料,邊學邊寫的產(chǎn)出,如有錯漏之處,還請不吝指教。
程序汪資料鏈接
堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階
臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!
臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!
字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!
歡迎添加程序汪個人微信 itwang008 進粉絲群或圍觀朋友圈
