一、什么是空指针异常
当程序需要对象实例的时候返回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
进行相等逻辑操作的时候,都进行了自动拆箱操作,a
为null
时造成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
值元素,比如ConcurrentHashMap
和Hashtable
。
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
或者value
为null
的情况下造成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特定的容器,后面讲到。