zoukankan      html  css  js  c++  java
  • 空指针异常与Optional类

    一、什么是空指针异常

    当程序需要对象实例的时候返回null就会抛出空指针异常(NullPointerException,简称NPE)。包括以下情况:

    • 调用一个null对象实例的方法
    • 访问或修饰null对象的字段
    • 获取数组为null时的长度
    • 访问或修饰数组为null时的索引值
    • 抛出Throwable对象为null时的异常

    虽然代码很难万无一失地避免所有NPE,但是也要尽量减少。所以一些防御性的编程技巧,可以将NPE控制在一个很好的水平上。

    空指针案例

    1. 调用业务方法的返回值对象

    在不清楚一个方法的返回值是否存在返回null的情况,直接使用对象返回值。

    People people = new People();
    People user = people.getUser("name");
    String name = user.getName();
    System.out.println(name);
    

    上面示例中的people.getUser("name");调用返回的对象不清楚是否为null,后面直接调用该对象的方法造成NPE。

    2. 包装类自动拆箱的值

    在包装类对象的值为null的情况下,进行自动拆箱操作。

    Integer a = null;
    int b = a;// System.out.println(1 == a);
    

    上面示例中包装类对象a定义时的初始化值为null,在将a赋值给基本数据类型的b的时候,以及与基本数据类型1进行相等逻辑操作的时候,都进行了自动拆箱操作,anull时造成NPE。

    3. 集合和数组空对象的遍历

    在不清楚集合或数组是否为null的时候,对它们进行遍历操作。

    List<String> list = null;// String[] list = null;
    for (String string : list) {   
        System.out.println(string);
    }
    

    在方法返回或者自己定义的数组和集合,只要有null的情况(不包括数组和集合长度为0的情况),进行遍历操作时造成NPE。

    4. Spring没注入实例的使用

    在使用Spring框架时,如果注入对象实例失败,此时该对象也是null

    public class BeanExample {   
        
        @Autowired    
        private BeanProvider beanProvider;
        
        public void run() {        
            this.beanProvider.sayHello(this.name, this.age);    
        }
    }
    

    当因为操作不当导致beanProvider没有注入,在调用sayHello()方法的时候造成NPE。

    5. ConcurrentHashMap和Hashtable的值

    对某些不支持null值的集合添加null值元素,比如ConcurrentHashMapHashtable

    ConcurrentHashMap<String, String> map = new ConcurrentHashMap();
    map.put("a", null);
    Hashtable<String, String> hashtable = newHashtable<>();
    hashtable.put("a", null);
    

    这些集合的底层put(K key,V value)方法中,在key或者valuenull的情况下会造成NPE。

    二、怎样防止空指针异常

    既然NPE难以避免,我们就要去找各种方法来解决。既要有良好的编码习惯,也要细心的去把控业务。

    1. 普通处理

    在针对调用业务方法进行NPE普通地防御,可以简单的添加非空判断。

    People people = new People();
    People user = people.getUser("name");
    if (user != null) {    
        String name = user.getName();    
        System.out.println(name);
    }
    

    2. 定义对象时的初始化

    在自己定义对象的时候,注意初始化的值可不可以为null

    // String str = "";             
    // 初始化为空字符串People people = newPeople();    
    // 初始化为对象
    People user = people.getUser("name");
    

    3. 使用 equals() 方法注意

    已知非空对象为调用方,比如将常量值、枚举值作为调用方,避免使用未知对象去调用方法,可有效避免NPE。

    String str = "123", string = null;
    System.out.println(str.equals(string));
    

    4. 使用 valueOf() 代替 toString() 方法

    使用toString()方法要利用对象去调用方法,而对象在不清楚是否为null的情况下,会抛出NPE。使用valueOf()方法可以避免使用未知对象去调用方法来避免。

    People people = null;
    System.out.println(String.valueOf(people));   // print:null
    System.out.println(people.toString());        // NPE
    

    5. 使用开源库非空判断方法

    推荐使用各大开源库的StringUtils字符串和CollectionUtils集合等工具进行非空判断。

    String str = null;
    List<String> list = null;
    if (!StringUtils.isEmpty(str)) {     
        System.out.println(str);
    }
    if (!CollectionUtils.isEmpty(list)) {     
        System.out.println(list);
    }
    

    6. 方法返回空集合或空数组

    在方法中返回空数组和空集合而不是返回null,JDK自带的Collections集合工具类提供了多种空集合的定义。

    public People[] getUsersArr() {     
        return new People[]{};
    } 
    public List<People> getUsers() {     
        // return Collections.emptyMap();     
        // return Collections.emptySet();     
        return Collections.emptyList();
    }
    

    7. 定义数据库字段是否为空

    在一些特定字段根据业务确定是否可为空,以及合理设置默认值。比如:表示业务状态的字段。

    CREATE TABLE user{    ...    status NOT NULL DEFAULT 0    ...}
    

    8. 使用JDK1.8的Optional类

    在JDK1.8后提供了防止NPE特定的容器,后面会讲到。

    三、Optional

    Optional是一个可以包含null或者非null的容器对象。根据源码分析方法功能:

    1. 定义方法

    1.1 empty

    返回一个空的Optaional实例,在这个实例中没有值存在。

    public static<T> Optional<T> empty() {    
        @SuppressWarnings("unchecked")    
        Optional<T> t = (Optional<T>) EMPTY;    
        return t;
    }
    

    1.2 of

    返回一个值不能为空的Optional实例,在这个实例中值为null时抛出NPE。

    public static <T> Optional<T> of(T value) {    
        return new Optional<>(value);
    } 
    private Optional(T value) {    
        this.value = Objects.requireNonNull(value);
    }
    

    1.3 ofNullable

    返回一个值可以为空的Optional实例,在这个实例中值为null时返回一个空的Optaional实例。

    public static <T> Optional<T> ofNullable(T value) {    
        return value == null ? empty() : of(value);
    }
    

    2. 功能方法

    2.1 isPresent

    如果有值存在,返回true,否则返回false

    People people = new People();
    System.out.println(Optional.ofNullable(people).isPresent());// print: true 
    people = null;
    System.out.println(Optional.ofNullable(people).isPresent());// print: false
    

    2.2 get()

    如果有值存在,返回值,否则抛出NoSuchElementException

    People people = new People();
    System.out.println(Optional.ofNullable(people).get());// print: People@61bbe9ba
    people = null;
    System.out.println(Optional.ofNullable(people).get());// print: Exception in thread "main" java.util.NoSuchElementException: No value present
    

    尽量不要使用该方法获取对象。

    2.3 orElse

    如果有值存在,返回值,否则返回该 orElse 方法的参数,可以用来定义默认值。

    String str = null;
    String result = Optional.ofNullable(str).orElse("default");//print:default
    System.out.println(result);
    

    2.4 orElseGet

    如果有值存在,返回值,否则返回提供者函数,可以用函数返回值来定义默认值。

    String str = null;
    String result = Optional.ofNullable(str).orElseGet(() -> "ajn");//print:ajn
    System.out.println(result);
    

    2.5 orElseThrow

    如果有值存在,返回值,否则返回函数接口参数提供的异常。

    String str = null;
    String result = Optional.ofNullable(str).orElseThrow(IllegalArgumentException::new);// print: Exception in thread "main" java.lang.IllegalArgumentException
    System.out.println(result);
    

    关于更多函数接口的内容,关注Java函数式编程

    2.6 ifPresent

    如果有值存在,方法参数提供的函数接口会进行处理,否则不做任何操作。

    Optional.ofNullable(getPeople()).ifPresent(people -> {    
        System.out.println("the people is nut null: " + people);
    });
    

    上面代码等价于:

    People people = getPeople();
    if (people != null) {    
        System.out.println("the people is nut null: " + people);
    }
    

    2.7 filter

    如果有值存在,并且值符合给定的函数条件,返回当前Optional,否则返回一个空的Optaional实例,可以用来过滤特殊值。

    String name = "AiJiangnan";// 给定的条件是字符串包含Ai
    String result = Optional.of(name).filter(str -> str.contains("Ai")).orElse("字符串不包含Ai");
    System.out.println(result);
    

    2.8 map

    如果有值存在,可以将该值的类型转换成其他类型,并返回转换后类型的Optional实例,否则返回一个空的Optaional实例,可以链式判空,非常实用。

    People people = null;
    String result = Optional.ofNullable(people)    
        .map(People::getName)    
        .map(String::toUpperCase)    
        .orElse("default");
    System.out.println(result);
    

    只有当people对象不为null,并且people.getName()不为null,才返回name全部转换为大写的字符串,否则都返回 default。

    2.9 flatMap

    如果有值存在,可以将该值的类型转换成其他类型,但最终只能转成 Optional 实例,否则返回一个空的Optaional实例。该方法与map方法类似,只是该方法返回的Optional实例由函数参数返回。

    People people = new People();
    people.setName(" ajn ");
    String result = Optional.ofNullable(people)    
        .flatMap(peo -> Optional.ofNullable(peo.getName()))    
        .flatMap(str -> Optional.of(str.toUpperCase()))    
        .orElse("default");
    System.out.println(result);
    
  • 相关阅读:
    Java多线程学习笔记
    Java核心技术(卷二)
    学习问题记录
    Java Web 学习杂记
    Java Web 学习笔记
    正则表达式学习记录
    k8s与docker版本依赖关系
    docker commit采坑记录
    关于docker的配置文件与环境变量的小发现
    搭建怎么搭建独立的sftp服务
  • 原文地址:https://www.cnblogs.com/kyoner/p/12101005.html
Copyright © 2011-2022 走看看