<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>

          Java Optional使用的最佳實踐

          共 16501字,需瀏覽 34分鐘

           ·

          2021-11-19 02:25

          ????關注后回復 “進群” ,拉你進程序員交流群????


          作者丨安琪拉

          來源丨安琪拉的博客


          我們經常在編程的遇到需要做空判斷的場景。

          例如拉哥最近遇到的一個場景,需要獲取任務節(jié)點的執(zhí)行完成時間,是Date類型的,但是上游需要時間的毫秒,所以寫了這么一段代碼

          public Result<TaskInfoVo> getTaskInfo(String taskId){
            TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
            //返回任務視圖
            TaskInfoVo taskInfoVo = TaskInfoVo
                                .builder()
                                .taskName(taskNode.getName())
                                .finishTime(taskNode.getFinishTime().getTime())
                       .user(taskNode.getUser())
                               .memo(taskNode.getMemo())
                               .build()));;

            return Result.ok(taskInfoVo);
          }

          class TaskInfoVo {
             /**
             ** 完成時間
             **/

             Long finishTime;
          }

          但是運行時發(fā)現任務的執(zhí)行時間可能為null,taskNode.getFinishTime().getTime() 這里會產生NPE(空指針異常)。

          如果不使用 Optional,一般判空的方式可以這么寫:

          //第一種判空
          if (Objects.notNull(taskNode.getFinishTime())) {
            taskInfoVo.set(taskNode.getFinishTime().getTime());
          }
          //第二種判空 保留builder模式
          TaskInfoVo
          .builder()
          .finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
          .build()));

          第一種方式就不能使用builder模式,值的設置割裂開了。

          第二種方式采用三目表達式也還算清晰,只是執(zhí)行了二次 getFinishTime(),如果在不使用Optional的二種方式,更推薦第二種,清晰一致。

          再說第三種,使用Optional 方式。如下所示:

          public Result<TaskInfoVo> getTaskInfo(String taskId){
            TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
            //返回任務視圖
            TaskInfoVo taskInfoVo = TaskInfoVo
                                .builder()
                                .taskName(taskNode.getName())
                                .finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date ->                        date.getTime()).orElse(null))
                       .user(taskNode.getUser())
                               .memo(taskNode.getMemo())
                               .build()));;

            return Result.ok(taskInfoVo);
          }

          首先,我們使用 Optional.ofNullable 把可能為空的值包了一層,然后用map和orElse 來設置存在值和為空分別的結果。

          我們來看看Optional 內部的實現細節(jié),代碼很清晰,也很簡單。

          /**
          **@since 1.8  jdk1.8引入
          **/

          public final class Optional<T// final 修飾,不能被繼承,也就是不允許重寫
           /**
               * Common instance for {@code empty()}. 空對象,但注意,不是null
               */

             private static final Optional<?> EMPTY = new Optional<>();
             /**
               * 存儲的值
               */

              private final T value;
              
              private Optional() {
                  this.value = null;
              }
             //空數據
              public static<T> Optional<T> empty() {
                  @SuppressWarnings("unchecked")
                  Optional<T> t = (Optional<T>) EMPTY;
                  return t;
              }

              //帶參構造函數
             private Optional(T value) {
                  //value 不能為空
                  this.value = Objects.requireNonNull(value);
              }

             //一般不建議使用of,因為傳入的值不允許為空,否則拋異常
             public static <T> Optional<T> of(T value) {
                  return new Optional<>(value);
              }
             
              //一般確定value不為null,才調用get
             public T get() {
                  if (value == null) {
                      throw new NoSuchElementException("No value present");
                  }
                  return value;
              }
            
             //是否存在 present是個好詞,源碼經常用
              public boolean isPresent() {
                  return value != null;
              }
              
              //如果存在,調用consumer 消費value值
             public void ifPresent(Consumer<? super T> consumer) {
                  if (value != null)
                      consumer.accept(value);
              }
              
              //判斷,predicate有test函數,filter返回過濾后Optional
             public Optional<T> filter(Predicate<? super T> predicate) {
                  Objects.requireNonNull(predicate);
                  if (!isPresent())
                      return this;
                  else
                      return predicate.test(value) ? this : empty();
              }
            
             //映射轉化 mapper
             public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
                  Objects.requireNonNull(mapper);
                  if (!isPresent())
                      return empty();
                  else {
                      return Optional.ofNullable(mapper.apply(value));
                  }
              }
              
              //和map相比,flatMap返回結果不能為空,否則拋NPE
             public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
                  Objects.requireNonNull(mapper);
                  if (!isPresent())
                      return empty();
                  else {
                      return Objects.requireNonNull(mapper.apply(value));
                  }
              }
              
              //值不為空 返回  否則返回其他
             public T orElse(T other) {
                  return value != null ? value : other;
              }
             
             //值不為空,返回,否則返回傳入的其他值
             public T orElseGet(Supplier<? extends T> other) {
                  return value != null ? value : other.get();
              }
             
             //值不為空,直接返回,否則丟出提供的異常
             public <X extends Throwable> orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
                  if (value != null) {
                      return value;
                  } else {
                      throw exceptionSupplier.get();
                  }
              }
          }

          //列出了 Objects
          class Objects {
              
              public static <T> requireNonNull(T obj) {
                  if (obj == null)
                      throw new NullPointerException();
                  return obj;
              }
            }

          我們來分別看一下這些方法的使用場景

          1. 希望為空時獲得默認值

            Task defalutTask = new Task("defalutTask", defalutTaskInfo);
            return Optional.ofNullable(task).orElse(defalutTask);
          2. 希望為空時進行函數求值

            Task defalutTask = new Task("defalutTask", defalutTaskInfo);
            return Optional.ofNullable(task).orElseGet(() -> assembleDefaultTask());

            private Task assembleDefaultTask() {
              Task currentTask = ExecutorManager.getCurrentTaskFromContext();
               currentTask.reset();
                return currentTask;
            }

            orElseGet 與  orElse 的區(qū)別在于 orElseGet 的參數是個 Supplier 對象,orElse 是值。

            Supplier 是java8 引入的。Supplier 接口僅包含一個無參的方法: T get() 。用來獲取一個泛型參數指定類型的對象數據。

            .orElseGet(() -> assembleDefaultTask());

          3. 空判斷

            if (taskOptional.isPresent()) {
                //doSomeThing();
            }
          4. 如果存在,執(zhí)行下一步消費者動作。

            taskOptional.ifPresent([Consumer]<? super [T]> consumer)
            //還是以日期處理為例
            Optional.ofNullable(previewStep.getFinishTime()).ifPresent(date ->                processPreviewVO.setFinishTime(date.getTime()));

            消費者大家不陌生,就是執(zhí)行一套消費動作,lamada 的寫法簡化了代碼,但是降低了可讀性,

            date -> processPreviewVO.setFinishTime(date.getTime())  實際上就是這段代碼:

            new Consumer<Date>() {
              @Override
              public void accept(Date date) {
                processPreviewVO.setFinishDate(date.getTime());
              }
            }
          5. orElse()  和 orElseGet() 的不同之處

            User user = null;
            logger.debug("Using orElse");
            User result = Optional.ofNullable(user).orElse(createNewUser());
            logger.debug("Using orElseGet");
            User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

            private User createNewUser() {
                logger.debug("Creating New User");
                return new User("[email protected]""1234");
            }

            輸出:

            Using orElse
            Creating New User
            Using orElseGet
            Creating New User

            如果user 為null 時結果是一致的;

            但是user 不為null 時,行為是不同的,我們來看下

            User user = new User("[email protected]""1234");
            logger.info("Using orElse");
            User result = Optional.ofNullable(user).orElse(createNewUser());
            logger.info("Using orElseGet");
            User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

            輸出:

            Using orElse
            Creating New User
            Using orElseGet

            為什么呢?

            因為orElse ( T value) 函數入參是普通變量,因為會將函數計算好的結果作為參數傳進去。

            但是orElseGet(Supplier<? extends T> other)入參實際上是個 Supplier 對象, 因為只有一個方法,所以可以用lambda寫法。

            public interface Supplier<T{

                /**
                 * Gets a result.
                 *
                 * @return a result
                 */

                get();
            }

            對象的函數沒有被調用是不會自己主動執(zhí)行的。

          6. 值轉化

            我們先來看map的例子,實際上面已經演示過了,下面再講解一下。

            User user = new User("[email protected]""niubi");
            String email = Optional.ofNullable(user)
              .map(u -> u.getEmail()).orElse("[email protected]");

            map可以對結果進行轉化,返回的是依然是Optional,方便你后續(xù)的鏈式調用。

          7. 值過濾

            User user = new User("[email protected]""666");
            Optional<User> result = Optional.ofNullable(user)
             .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

            這個filter 方法實際上類似斷言,我總覺得對集合元素進行遍歷,判斷是否符合預期才更適合叫做 filter ,這個功能用的比較少。

          8. Optional類的鏈式方法

            比如現在我們有這個一個場景,智能游戲機器人需要讓安琪拉釋放火球技能,它需要判斷自己英雄池是否有安琪拉,并且安琪拉火球技能是否ready。


            代碼實現:

            String result = Optional.ofNullable(heroPool)
                  .flatMap(heroPool -> heroPool.getHero("angela"))
                  .flatMap((Angela)hero -> hero.getFireBall())
                  .map(c -> c.fire())
                  .orElse("failure");

            如果用常規(guī)代碼,需要做很多層判空處理。


          9. 注意事項

            • 不要將null賦給Optional  雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional

            • 盡量避免使用Optional.get()

            • 當結果不確定是否為null時,且需要對結果做下一步處理,使用Optional;

            • 在類、集合中盡量不要使用Optional 作為基本元素;

            • 盡量不要在方法參數中傳遞Optional;

            • 大膽使用map、filter 重構代碼

              //1. map 示例
              if ( hero != null){
                 return "hero : " + hero.getName() + " is fire...";
               } else { 
                 return "angela";
               }
               //重構成
               String heroName = hero
               .map(this::printHeroName)
               .orElseGet(this::getDefaultName);

              public void printHeroName(Hero dog){
                 return  "hero : " + hero.getName() + " is fire...";
              }
              public void getDefaultName(){
                 return "angela";
              }

              //2. filter示例
              Hero hero = fetchHero();
              if(hero != null && hero.hasBlueBuff()){
                hero.fire();
              }

              //重構成
              Optional<Hero> optionalHero = fetchHero();
              optionalHero
               .filter(Hero::hasBlueBuff)
               .ifPresent(this::fire);
            • 不要使用 Optional 作為Java Bean Setter方法的參數

              因為Optional 是不可序列化的,而且降低了可讀性。

            • 不要使用Optional作為Java Bean實例域的類型

              原因同上。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網盤了,歡迎下載!

          點擊??卡片,關注后回復【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  一级a免一级a做片免费 | 手机免费毛片 | 丁香婷婷在线视频 | 色五月乱伦 | 超碰99在线 |