JDK8-Optional类避免NPE

1.需求分析

调用RPC返回结果、包装类转简单类型,拆箱过程最容易出现NPE问题。JDK8之前需要小心翼翼对各种类型进行判空,可通过JKD8-Optional类来预发未知的NPE错误。

2.关于避免NPE的常识

  • 【强制】所有POJO类属性必须使用包装数据类型
  • 【强制】所有RPC方法的返回和参数必须使用包装数据类型
  • 【推荐】所有局部变量使用基本简单数据类型
  • Ps:
    • POJO类属性没有初值是提醒使用者在需要使用时,必须自己显示地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
    • 正例:数据库的检查结果可能是null,因为自动拆箱,用基本数据类型接受有NPE风险。
    • 反例:比如显示成交总额涨跌情况,即正负x%,x为基本简单类型,调用RPC服务,调用不成功时,返回的是默认值,页面显示为0%,显然这是不合理的,应该显示成-%。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败、异常推出。
  • 【推荐】防止NPE,是程序员基本修养,注意NPE产生的场景。
    • 1)返回类型为基本简单数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。
    • 反例: public int func(){ return Integer对象}; 如果为null,自动拆箱抛出NPE。
    • 2)数据库的查询结果可能为null。
    • 3)集合里的元素即使isNotEmpty,取出的元素仍有可能为null。
    • 4)远程调用返回对象时,一律要求进行空指针判断,防止NPE。
    • 5)对于Session中获取的数据,建议NPE检查,避免空指针。
    • 6)级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
    • 正例:使用JDK8的Optional类防止NPE问题。

3.JDK8-Optinal类防止NPE

JDK8增加了许多有用的API,Optional类就是其中之一。如果不了解Optional类,只是简单的认为它可以解决NPE问题,于是就有了如下代码:
    Optional<User> user = xxx
    if(user.isPresent){
        return user.getOrders();
    }else {
        return Collections.emptyList();
    }

上面采用Optional类的代码和我们正常下面写法并没有太大区别:

    User user = xxx
    if(user!=null){
        return user.getOrders();
    }else{
        return Collections.emptyList();
    }

两者没有实质的区别,那么Optinal的正确使用姿势来了:

  • 先来了解下Optional常用的方法有哪些?
   1.public<U> Optional<U> map(Function<? super T,? extends U>mapper)
   2.public T orElse(T other)
   3.public T orElseGet(Supplier <? extends T> other)
   4.public void ifPresent(Consumer <? super T> consumer)
   5.public Optional <T> filter(Predicate <? supper T> predicate)
   6.public <U> OPtional <U> flatMap(Function <? supper T,Optional <U> mapper>>)
   7.public <X extends Throwable T> orElseThrow(Supplier <? extends X> exceptionSupplier) throws X
  • Optional的三种构造方法:
    • Optional.of(obj):要求传入obj不能为null,否则还没开始初始化就NPE
    • Optional.ofNullable(obj):以一种智能、宽容的方式来构造一个Optional实例,来者不拒。传参为null时,就得到Optional.empty(),非null,则调用Optional.of(obj)
    • Optional.empty():返回空obj
  • 作者的观点:1.当我们非常非常确定要传给Optional.of(obj)的obj参数不可能为null时,比如它是一个刚new出来的对象(Optional.of(new User(…))),或者是一个非null的常量时。2.当想为obj断言不为null时,即我们想在玩意obj为null立即报告NPE异常,立即修改,而不是隐藏空指针异常时,我们就应该果断调用Optional.of(obj)来构造Optional实例,而不让任何不可预计的null值有可乘之机隐身于Optionl中。
  • 存在即返回,无则提供默认值

   //替代 return user.isPresent()?user.get:null;
   return user.orElse(null);
   return user.orElse(UNKNOW_USER);
  • 存在即返回,无则由函数产生

    //替代 return user.isPresent()?user:fetchAUserFromDatabase();
    return user.orElse(()->fetchAUserFromDatabase());
  • 存在才对它做点什么
    user.ifPresent(System.out::printIn);
    
    //替代下面
    if(user.isPresent()){
        System.out.printIn();
    }
  • user.isPresent()为true时,获得它关联的orders,为false时则返回一个空集合。那么我们用上面的orElse、orElseGet方法都乏力。这里可以采用map函数,只需要下面这一行代码:
    return user.map(u->u.getOrders()).orElse(Collections.emptyList());
    
    //替代下面繁琐的判断
    if(user.isPresent()){
        return user.get().getOrders();
    }else{
        retrun Collections.emptyList();
    }
  • map可能是无限级联的,比如多层,例如:
    return user.map(u->u.getUserName()).map(name->name.toUpperCase()).orElse(null);
  • JDK8之前的做法都是一层一层的展开,例如:
    if(user!=null){
        String userName=user.getUserName();
        if(userName!=null){
            return name.toUpperCase();
        }else{
            return null;
        }
    }else{
        return null;
    }

小结:使用Optional时尽量不要直接调用Optional.get()方法,Optional.isPresent()更应该被视为一个私有方法,应依赖于其他像:Optional.orElse(),Optional.orElseGet(),Optional.map()等方法。建议深入了解JDK8的源码。

参考地址:
阿里巴巴开发手册,
JDK8-Optional类解决NPE

你可能感兴趣的:(Java基础学习)