开发小技巧系列 - 如何避免NPE,去掉if...else(四)

开发小技巧系列文章,是本人对过往平台系统的设计开发及踩坑的记录与总结,给初入平台系统开发的开发人员提供参考与帮助

在前面的三个章节中,对如何避免NPE,及程序中如果简化null != obj场景进行了讲解,及给出了解决方案,但回头去看写的Utils工具类,总感觉还缺少点什么,看着if ... else .... 总感觉不爽,那还有什么解决办法吗?
带着if ... else ... 的代码:

        /**
         * 转换null值的模板方法
         * @param value
         *  输入的值
         * @param defaultValue
         *  默认值
         * @param <R>
         *     输入的对象的类型
         * @return
         */
        public static <R> R wrapNull(R value, R defaultValue){
            Optional optional = Optional.ofNullable(value);
            if(optional.isPresent()){
                return value;
            }
            return defaultValue;
        }
        /**
         * 转换输入的整数为null的情况
         * @param input
         *      输入数值
         * @param defaultValue
         *      指定默认值
         * @return
         *      如果是null,则返回 defaultValue
         */
        public static Integer nullInt(Integer input, Integer defaultValue){
            Optional optional = Optional.ofNullable(input);
            if(optional.isPresent()){
                return input;
            }
            return defaultValue;
        }


    带着这个疑问,打开了Optional的源代码。

      package java.util;
      ....
      public final class Optional<T> {   ... public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } /**        * 返回一个值不为Null的 {@code Optional} 对象 .  *   * @param <T> 对象的class      * @param   非空的对象(值) * @return an {@code Optional} with the value present      * @throws 如果值为NULL, 会抛出NullPointerException */ public static <T> Optional<T> of(T value) { return new Optional<>(value); }
      /**      * 返回一个输入对象(值)的 {@code Optional} 对象(值),       * 如果值为空,则返回一个 empty {@code Optional}. * * @param <T> 对象(值)的class * @param 输入的值(对象)      * @return 非空则返一个Optional的对象(值),传入的值为null,则返回一个EMPTY对象      */     public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); }     //在往下看,还可以发现他还支持map, flatmap, orElse等方法     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)); } }         //值转化函数     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)); } }
          /**      * 返回当前Optional中的值(对角)      * 如果不存在,则返回 {@code other}. *      * @param other 当前Optional的值不存在时,需要返回的值(一般用于默认赋值的场景)      * @return 略 */ public T orElse(T other) { return value != null ? value : other; }     ...省略 }

      看到这里,在回头看原来写的if ... else ..., 原来还可以这样,在看修改的代码前,先来看下Optional的这几个api的说明与用途。

      从Optional的源码上看,Optional是一个final类,不可能在被继承,而且它的构造函数是private(不可见的),不能直接通过new XXX(...)这样来创建一个新的对象。但它提供了3个public的静态构造方法,用来构造一个带真实值的Optional对象,和一个代表空值的对象EMPTY。即,of(T value)、ofNullable(T value)、 empty(),在这向个函数的内部都是调用内部的构造方法。

      • of(T value) : 传入的 value 值或对角不能为Null,如果为Null,则会抛出NullPointerException的异常。
      • ofNullable(T value) : 可以传入任意的值或对象,包括Null,都不会抛出NullPointerException的异常。
      • empty() :构造一个 value = null的空对象。

      在往下看,可以看到它还有其他的api方法,如fliter(...)、map(...)、flatMap(...)、orElse(...)、orElseGet(...)、orElseThrow(...)等,从这些方法的定义上看,都是支持JDK1.8中的lamda表达式的语法。

      • 数据转换:map(...), flatMap(...)
      • 分支:orElse(...)、orElseGet(...)、orElseThrow(...)
      • 过滤:filter(...),返回满足条件的数值,如果不满足,会返回empty

      好了,通过对api的学习,原来还可以这样简单(见ValueUtils.java, ValueUtilsV2.java),像

        /**
             * 转换null值的模板方法
             * @param value
             *  输入的值
             * @param defaultValue
             *  默认值
             * @param <R>
             *     输入的对象的类型
             * @return
             */
            public static <R> R wrapNull(R value, R defaultValue){
                Optional optional = Optional.ofNullable(value);
                if(optional.isPresent()){
                    return value;
                }
                return defaultValue;
            }

        可以修改为

          /**
               * 转换null值的模板方法
               * @param value
               *  输入的值
               * @param defaultValue
               *  默认值
               * @param <R>
               *     输入的对象的类型
               * @return
               */
              public static <R> R wrapNull(R value, R defaultValue){
                  return Optional.ofNullable(value).orElse(defaultValue);
              }

          像这样的

            /**
                 * 转换BigDecimal(浮点数)数值可能为null的情况
                 * @param value
                 *      输入数值
                 * @param scale
                 *      指定小数位
                 * @param defaultValue
                 *      指定默认值
                 * @return
                 *      默认四舍五入
                 *      如果为null,则返回 defaultValue
                 */
                public static BigDecimal wrapNull(BigDecimal value, int scale, BigDecimal defaultValue){
                    Optional optional = Optional.ofNullable(value);
                    if(optional.isPresent()){
                        return value.setScale(scale, BigDecimal.ROUND_HALF_UP);
                    }
                    return defaultValue;
                }

            可以改成

              /**
                   * 转换BigDecimal(浮点数)数值可能为null的情况
                   * @param value
                   *      输入数值
                   * @param scale
                   *      指定小数位
                   * @param defaultValue
                   *      指定默认值
                   * @return
                   *      默认四舍五入
                   *      如果为null,则返回 defaultValue
                   */
                  public static BigDecimal wrapNull(BigDecimal value, int scale, BigDecimal defaultValue){
                      return Optional.ofNullable(value).map(e->e.setScale(scale, BigDecimal.ROUND_HALF_UP))                .orElse(defaultValue);
                  }

              OptionalUtils.java中的修改:

                   /**
                     * 属性互转模板方法
                     * @param source
                     *  原对象(需要比较的对象)
                     * @param function
                     *  对象E->R的赋值过程
                     * @param defaultObject
                     *  默认值
                     * @param <E>
                     *      原始对象
                     * @param <R>
                     *      结果对象
                     * @return
                     */
                    public static <E, R> R transfer(E source, Function<E, R> function, R defaultObject){
                        //旧的逻辑处理逻辑
                        //Optional<E> optional = Optional.ofNullable(source);
                        //if(optional.isPresent()){
                        //return function.apply(optional.get());
                        //}
                        //return defaultObject;
                
                //利用orElse后的处理逻辑 return Optional.ofNullable(source).map(e->function.apply(e)).orElse(defaultObject); }

                代码调整完了,看看单元测试的结果

                  /**
                       * 测试正常情况下的调用(非null)
                       */
                      @Test
                      public void optionalTest(){
                          // 声明一个会员
                          Member member = new Member();
                          member.setId(1);
                          member.setMemberId(1000);
                          member.setNickName("测试");
                          member.setGender(1);
                  MemberDTO memberDTO = memberService.transferByOptional(member, null); log.debug("{}", memberDTO);
                  //声明一个部门 Dept dept = new Dept(); dept.setId(1); dept.setDeptId(100); dept.setParentId(0); dept.setDeptName("部门");
                  DeptDTO deptDTO = OptionalUtils.transfer(dept, MemberService::cover, null); log.debug("deptDTO : {}", deptDTO); }
                    17:03:16.645 [main] DEBUG net.jhelp.demo.OptionalTest - MemberDTO(memberId=1000, nickName=测试, realName=null, gender=1, genderName=男)
                    17:03:16.674 [main] DEBUG net.jhelp.demo.OptionalTest - deptDTO : DeptDTO(deptId=100, parentId=0, deptName=部门)
                      /**
                           *  测试传入对象是null的情况
                           */
                          @Test
                          public void optionalWithNullTest2(){
                              MemberDTO2 memberDTO = memberService.transferByOptional2(null, MemberDTO2.EMPTY_MEMBER);
                              log.debug("optionalWithNullTest2:{}", memberDTO);
                              log.debug("是否是空对象:{}", memberDTO.isEmpty());
                          }
                        17:04:42.572 [main] DEBUG net.jhelp.demo.OptionalTest - optionalWithNullTest2:MemberDTO2(memberId=null, nickName=null, realName=null, gender=null, genderName=null)
                        17:04:42.583 [main] DEBUG net.jhelp.demo.OptionalTest - 是否是空对象:true

                        具体的代码调整,可以下载测试的demo程序。

                        https://gitee.com/TianXiaoSe_admin/java-npe-demo.git

                        总结一下

                        由于Optional中加了lamda的新特性,代码是简化,但可能不易理解,还是要根据实际的需要来使用。

                        思考、偿试比结论重要,希望你能从中有所收获。


                        开发小技巧系列文章:

                        1. 开发小技巧系列 - 库存超卖,库存扣成负数?

                        2. 开发小技巧系列 - 重复生成订单

                        3. 开发小技巧系统 - Java实现树形结构的方式有那些?

                        4. 开发小技巧系列 - 如何避免NullPointerException?(一)

                        5. 开发小技巧系列 - 如何避免NullPointerException?(二)

                        6.开发小技巧系列 - 如何避免NPE,巧用Optional重构三元表达式?(三)

                        腾讯云推出云产品限时特惠抢购活动:2C2G云服务器7.9元/月起
                        本文链接:https://www.jhelp.net/p/1MMIAJLQXLhswKBX (转载请保留)。
                        关注下面的标签,发现更多相似文章