<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Java Optional使用的最佳實踐

    共 16501字,需瀏覽 34分鐘

     ·

    2021-11-19 02:25

    ????關(guān)注后回復(fù) “進群” ,拉你進程序員交流群????


    作者丨安琪拉

    來源丨安琪拉的博客


    我們經(jīng)常在編程的遇到需要做空判斷的場景。

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

    public Result<TaskInfoVo> getTaskInfo(String taskId){
      TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
      //返回任務(wù)視圖
      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ā)現(xiàn)任務(wù)的執(zhí)行時間可能為null,taskNode.getFinishTime().getTime() 這里會產(chǎn)生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模式,值的設(shè)置割裂開了。

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

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

    public Result<TaskInfoVo> getTaskInfo(String taskId){
      TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
      //返回任務(wù)視圖
      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 來設(shè)置存在值和為空分別的結(jié)果。

    我們來看看Optional 內(nèi)部的實現(xiàn)細節(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;
        }
       //空數(shù)據(jù)
        public static<T> Optional<T> empty() {
            @SuppressWarnings("unchecked")
            Optional<T> t = (Optional<T>) EMPTY;
            return t;
        }

        //帶參構(gòu)造函數(shù)
       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,才調(diào)用get
       public T get() {
            if (value == null) {
                throw new NoSuchElementException("No value present");
            }
            return value;
        }
      
       //是否存在 present是個好詞,源碼經(jīng)常用
        public boolean isPresent() {
            return value != null;
        }
        
        //如果存在,調(diào)用consumer 消費value值
       public void ifPresent(Consumer<? super T> consumer) {
            if (value != null)
                consumer.accept(value);
        }
        
        //判斷,predicate有test函數(shù),filter返回過濾后Optional
       public Optional<T> filter(Predicate<? super T> predicate) {
            Objects.requireNonNull(predicate);
            if (!isPresent())
                return this;
            else
                return predicate.test(value) ? this : empty();
        }
      
       //映射轉(zhuǎn)化 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返回結(jié)果不能為空,否則拋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. 希望為空時進行函數(shù)求值

      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 的參數(shù)是個 Supplier 對象,orElse 是值。

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

      .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 時結(jié)果是一致的;

      但是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) 函數(shù)入?yún)⑹瞧胀ㄗ兞?,因為會將函?shù)計算好的結(jié)果作為參數(shù)傳進去。

      但是orElseGet(Supplier<? extends T> other)入?yún)嶋H上是個 Supplier 對象, 因為只有一個方法,所以可以用lambda寫法。

      public interface Supplier<T{

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

          get();
      }

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

    6. 值轉(zhuǎn)化

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

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

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

    7. 值過濾

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

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

    8. Optional類的鏈式方法

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


      代碼實現(xiàn):

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

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

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

      • 盡量不要在方法參數(shù)中傳遞Optional;

      • 大膽使用map、filter 重構(gòu)代碼

        //1. map 示例
        if ( hero != null){
           return "hero : " + hero.getName() + " is fire...";
         } else { 
           return "angela";
         }
         //重構(gòu)成
         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();
        }

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

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

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

        原因同上。

    -End-

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

    點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

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

    瀏覽 29
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

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

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    操逼视频大片 | 日本一区二区福利视频 | 无码人妻AV | 欧美一级黄色大片 | FC2清純18歲在线播放 |