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

简介: 利用optional来处理各种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重构三元表达式?(三)

目录
相关文章
|
2月前
|
Java 程序员 测试技术
我有一个朋友写出了17种触发NPE的代码!避免这些坑
我有一个朋友,写代码的时候常常遭到NPE背刺,痛定思痛,总结了NPE出没的17个场景,哪一个你还没有遇到过?
|
2月前
|
前端开发 Java 测试技术
JavaSE碎碎念:记录一次异常测试和一些个人的浅显理解
JavaSE碎碎念:记录一次异常测试和一些个人的浅显理解
|
2月前
|
IDE Java 测试技术
java异常处理及错误调试技巧
java异常处理及错误调试技巧
17 0
|
2月前
|
设计模式 存储 算法
谈谈代码:如何避免写出糟糕if...else语句
在写代码的日常中,`if...else`语句是极为常见的.正因其常见性,很多同学在写代码的时候并不会去思考其在目前代码中的用法是否妥当.而随着项目的日渐发展,糟糕的`if...else`语句将会充斥在各处,让项目的可维护性急剧下降.故在这篇文章中,笔者想和大家谈谈如何避免写出糟糕`if...else`语句.
32 0
谈谈代码:如何避免写出糟糕if...else语句
|
2月前
|
Java
Java【问题记录 03】三目运算符失效问题刨根问底(及NPE分析)
Java【问题记录 03】三目运算符失效问题刨根问底(及NPE分析)
32 1
|
Java 测试技术
开发小技巧系列 - 如何避免NPE,巧用Optional重构三元表达式?(三)
NPE是一个老生长谈的问题,无论新手,还是老手,在开发程序的过程中,都不可避免会遇到,而为了处理NPE,往往需要添加很多重复性的检查代码,又长又臭。NPE系列文章,是总结了过往的开发经验,助力更多新手,避免踩坑。
74 0
|
缓存 搜索推荐 Java
开发小技巧系列 - 如何避免NullPointerException?(二)
NPE问题处理之二,引入了optional来处理,还有空对象
63 0
|
SQL 前端开发 Java
开发小技巧系列 - 如何避免NullPointerException?(一)
NPE是JAVA界面的常见问题,如何来避免呢?
74 0
|
Java 程序员 数据库
Java异常处理机制(完结篇)——再看不懂我找不到女朋友
Java异常处理机制(完结篇)——再看不懂我找不到女朋友
120 0
Java异常处理机制(完结篇)——再看不懂我找不到女朋友
|
测试技术
软件测试面试题:SoftAssert的特点,并对软断言进行举例说明?
软件测试面试题:SoftAssert的特点,并对软断言进行举例说明?
95 0