<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java8 Optional 最佳實踐

          共 7523字,需瀏覽 16分鐘

           ·

          2021-07-27 02:55

          作者 | ES_her0

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

          很多公眾號其實都發(fā)過 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帶來的錯誤,我們提供了一個可以明確表示空值的有限的機制。

          基礎(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,增加了代碼的復雜性,沒有帶來任何實質(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)造方法中使用Optional
          • Optional屬于返回類型,在業(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來增加復雜性。

          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也是徒增復雜,收益甚微。例如,map 已經(jīng)有了getOrDefault()這樣的類似orElse()的 API 了。

          總結(jié)

          Optional的出現(xiàn)使 Java 對 null 的表達能力更近了一步,好馬配好鞍,合理使用可以避免大量的 NPE,節(jié)省大量的人力物力。以上內(nèi)容也是本人查詢了很多資料,邊學邊寫的產(chǎn)出,如有錯漏之處,還請不吝指教。

          往期推薦

          令人笑噴的56個代碼注釋,你寫過多少?

          還在用 Random生成隨機數(shù)?試試 ThreadLocalRandom,超好用!

          消息冪等(去重)通用解決方案,真頂!

          最強代碼生成器平臺,殺瘋了!

          Spring為什么建議使用構(gòu)造器來注入?



          喜歡本文歡迎轉(zhuǎn)發(fā),關(guān)注我訂閱更多精彩

          關(guān)注我回復「加群」,加入Spring技術(shù)交流群

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操的好爽视频 | 久久精品苍井空免费一区 | 成人影音先锋在线资源 | 一级特黄色 | 日本精品18禁 |