Java 8 Optional 最佳實踐

點擊下方“IT牧場”,選擇“設爲星標”

作者 | ES_her0

來源 |  https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be

很多公衆號其實都發過 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源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章