美文网首页
NPE异常的常见诱因及预防方法

NPE异常的常见诱因及预防方法

作者: 文景大大 | 来源:发表于2020-02-23 17:43 被阅读0次

一、什么是NPE?

NullPointerException,空指针异常,简称NPE,是程序开发中最常遇到的异常之一,也是开发人员最容易忽略处理的异常之一,它产生了大量的生产问题,值得开发人员深刻了解它产生的原因和常见的处理办法。

为什么它是最常遇到的异常呢?

因为产生NPE的场景非常多,在其源码的注释里面就列举了5中场景:

  • 1.调用null对象实例的方法;
  • 2.访问或者修改null对象的属性;
  • 3.获取值为null的数组的长度;
  • 4.访问或者修改值为null的数组值时;
  • 5.把null当做Throable对象抛出时;

在实际的编码场景中,产生NPE问题的原因都在这些情况里面,或者是这些情况的相似场景。文章下面会举例子详细予以说明。

为什么开发人员容易忽略这种异常呢?

因为NPE是一种运行时异常,不是编译型异常,所以,IDE不会及时地给出提示。关于异常的分类和继承体系,可以参考另一篇文章:《关于Exception异常处理的建议》

其次,倘若程序可以正常执行,也不会及时提示有NPE风险,除非真的遇到上述5种情况,程序才会报错崩溃;再加上开发人员没有良好的编程习惯,对产生NPE的场景不了解,也没有对代码进行边界值测试,所以非常容易遗留NPE异常到生产环境。

现在有一些静态代码检查工具,诸如Fortify、Sonar、Alibaba Java Coding Guidelines等,可以在你写完代码进行检查时提示你某些地方可能存在NPE风险;

除了借助这些代码检查工具,最好就是看完这篇文章,大致掌握产生NPE的原因及场景,下次开发时做到心中有数,就不太会忘记检查啦。还有,单元测试的测试用例一定要设计全,考虑到边界值的情况。

二、常见的导致NPE的诱因

2.1 迭代值为null的数组/集合

    public static void main(String[] args) {
        List<Fruit> fruitList = new ArrayList<>();
        for(Fruit apple : fruitList){
            log.info("{}",apple);
        }
    }

    private static List<Fruit> getFruitList(){
        return null;
    }

我们使用如上new ArrayList<>()为集合赋值是没问题的,它只是一个空集合,但不是null,所以接下里的迭代不会报错,但是有时,我们的集合是null值,比如改成:

List<Fruit> fruitList = getFruitList();

那么在迭代中就会报NPE了,这个和文章开始时罗列场景中的第4点很像,因为将List换成数组Array结果是一样的。

2.2 调用null对象实例的方法

    public static void main(String[] args) {
        Fruit apple = getFruit();
        // apple对象并不存在,为null
        log.info("fruit name is : {}", apple.getName());
    }

    private static Fruit getFruit(){
        return null;
    }

2.3 访问或修改null对象的属性

    public static void main(String[] args) {
        Fruit apple = getFruitList();
        // 虽然apple对象存在,但是其name属性对象为null
        if(apple.getName().equals("apple")){
            log.info("great!");
        }
    }

    private static Fruit getFruitList(){
        return new Fruit();
    }

所以,我们一般写成if("apple".equals(apple.getName()))来避免潜在的NPE风险。

2.4 自动拆箱产生的NPE

    public static void main(String[] args) {
        getFruitWeight();
    }

    private static int getFruitWeight(){
        Integer currentWeight = null;
        // 自动将Integer拆箱为int返回给调用方
        return currentWeight;
    }

2.5 if及switch条件为null产生的NPE

    public static void main(String[] args) {
        // 没有对if条件进行非空检查,导致NPE
        if(isGood()){
            log.info("good!");
        }
    }

    private static Boolean isGood(){
        return null;
    }
    public static void main(String[] args) {
        // 没有对switch条件进行非空检查,导致NPE
        switch(getWeight()){
            case 1 : log.info("1");
            case 2: log.info("2");
            default: log.info("good");
        }
    }

    private static Integer getWeight(){
        return null;
    }

如上是比较常见的NPE场景,当然还有其它场景,日后积累了再来补充。

三、常见的预防NPE的方法

3.1 使用if检查

在使用对象,或者对象的属性时,进行非空的检查。

倘若此处业务场景要求一定不能为空,可以抛出异常;倘若不影响,可以直接跳过受null影响的代码块继续执行。

        if(apple == null){
            // 业务场景要求一定不能为空
            throw new IllegalArgumentException("apple为空!");
        }
         // 跳过受null影响的代码块继续执行
        if(apple != null){
            log.info("fruit name is : {}", apple.getName());
        }
        log.info("good");

3.2 使用工具类检查

如果每次使用对象或者对象的属性时都要写上一大段if判断非空,程序将会变得臃肿丑陋,我们可以使用一些类库来简化完成这样的工作。

  • 使用Objects类

    Objects.requireNonNull(apple,"apple对象不能为空!");
    

    去看源码就会发现,如上代码做的事情和我们自己写if判断是一样的。

  • 使用commons-lang3

    Validate.notNull(apple,"apple对象不能为空!");
    
    Validate.notEmpty(fruitList,"fruitList不能为null且不能为空!");
    

    如果集合fruitList为Null,就会报NPE;如果为空列表,就会报IllegalArgumentException

  • 使用commons-collections4或者spring自带的集合工具类CollectionUtils;

  • 使用guava的前置条件检测类

3.3 使用注解声明

  • 使用lombok的@NonNull

        public static void main(String[] args) {
            Fruit apple = null;
            cutFruit(apple);
        }
    
        private static void cutFruit(@NonNull Fruit fruit){
            log.info("{} cut", fruit.getName());
        }
    
  • 使用IDEA的@NotNull

  • 使用Spring的@Validated;

    前面的都是给整个对象判断是否为空,但是如果一个对象有很多属性,我们也需要判断这些属性是否为空怎么办呢,总不能一个个地手写if检查吧,此时,可以使用@Validate功能,在JavaBean上做非空约束。

    @Data
    public class Fruit{
        @NotBlank(message="name不能为空")
        private String name;
        private Integer weight;
        @NonNull
        private Shop shop;
    }
    

    全文完。

相关文章

网友评论

      本文标题:NPE异常的常见诱因及预防方法

      本文链接:https://www.haomeiwen.com/subject/ctfsqhtx.html