当程序需要对象实例的时候返回 null
就会抛出空指针异常( NullPointerException
,简称 NPE
)。包括以下情况:
null
对象实例的方法 null
对象的字段 null
时的长度 null
时的索引值 Throwable
对象为 null
时的异常 虽然代码很难万无一失地避免所有NPE,但是也要尽量减少。所以一些防御性的编程技巧,可以将NPE控制在一个很好的水平上。
在不清楚一个方法的返回值是否存在返回 null
的情况,直接使用对象返回值。
People people = new People(); People user = people.getUser("name"); String name = user.getName(); System.out.println(name);
上面示例中的 people.getUser("name");
调用返回的对象不清楚是否为 null
,后面直接调用该对象的方法造成NPE。
在包装类对象的值为 null
的情况下,进行自动拆箱操作。
Integer a = null; int b = a;// System.out.println(1 == a);
上面示例中包装类对象 a
定义时的初始化值为 null
,在将 a
赋值给基本数据类型的 b
的时候,以及与基本数据类型 1
进行相等逻辑操作的时候,都进行了自动拆箱操作, a
为 null
时造成NPE。
在不清楚集合或数组是否为 null
的时候,对它们进行遍历操作。
List<String> list = null;// String[] list = null; for (String string : list) { System.out.println(string); }
在方法返回或者自己定义的数组和集合,只要有 null
的情况(不包括数组和集合长度为0的情况),进行遍历操作时造成NPE。
在使用Spring框架时,如果注入对象实例失败,此时该对象也是 null
。
public class BeanExample { @Autowired private BeanProvider beanProvider; public void run() { this.beanProvider.sayHello(this.name, this.age); } }
当因为操作不当导致 beanProvider
没有注入,在调用 sayHello()
方法的时候造成NPE。
对某些不支持 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难以避免,我们就要去找各种方法来解决。既要有良好的编码习惯,也要细心的去把控业务。
在针对调用业务方法进行NPE普通地防御,可以简单的添加非空判断。
People people = new People(); People user = people.getUser("name"); if (user != null) { String name = user.getName(); System.out.println(name); }
在自己定义对象的时候,注意初始化的值可不可以为 null
。
// String str = ""; // 初始化为空字符串People people = newPeople(); // 初始化为对象 People user = people.getUser("name");
已知非空对象为调用方,比如将常量值、枚举值作为调用方,避免使用未知对象去调用方法,可有效避免NPE。
String str = "123", string = null; System.out.println(str.equals(string));
使用 toString()
方法要利用对象去调用方法,而对象在不清楚是否为 null
的情况下,会抛出NPE。使用 valueOf()
方法可以避免使用未知对象去调用方法来避免。
People people = null; System.out.println(String.valueOf(people)); // print:null System.out.println(people.toString()); // NPE
推荐使用各大开源库的 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); }
在方法中返回空数组和空集合而不是返回 null
,JDK自带的 Collections
集合工具类提供了多种空集合的定义。
public People[] getUsersArr() { return new People[]{}; } public List<People> getUsers() { // return Collections.emptyMap(); // return Collections.emptySet(); return Collections.emptyList(); }
在一些特定字段根据业务确定是否可为空,以及合理设置默认值。比如:表示业务状态的字段。
CREATE TABLE user{ ... status NOT NULL DEFAULT 0 ...}
在JDK1.8后提供了防止NPE特定的容器,后面会讲到。
Optional
是一个可以包含 null
或者非 null
的容器对象。根据源码分析方法功能:
返回一个空的 Optaional
实例,在这个实例中没有值存在。
public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }
返回一个值不能为空的 Optional
实例,在这个实例中值为 null
时抛出NPE。
public static <T> Optional<T> of(T value) { return new Optional<>(value); } private Optional(T value) { this.value = Objects.requireNonNull(value); }
返回一个值可以为空的 Optional
实例,在这个实例中值为 null
时返回一个空的 Optaional
实例。
public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); }
如果有值存在,返回 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
如果有值存在,返回值,否则抛出 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
尽量不要使用该方法获取对象。
如果有值存在,返回值,否则返回该 orElse
方法的参数,可以用来定义默认值。
String str = null; String result = Optional.ofNullable(str).orElse("default");//print:default System.out.println(result);
如果有值存在,返回值,否则返回提供者函数,可以用函数返回值来定义默认值。
String str = null; String result = Optional.ofNullable(str).orElseGet(() -> "ajn");//print:ajn System.out.println(result);
如果有值存在,返回值,否则返回函数接口参数提供的异常。
String str = null; String result = Optional.ofNullable(str).orElseThrow(IllegalArgumentException::new);// print: Exception in thread "main" java.lang.IllegalArgumentException System.out.println(result);
关于更多函数接口的内容,关注Java函数式编程
如果有值存在,方法参数提供的函数接口会进行处理,否则不做任何操作。
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); }
如果有值存在,并且值符合给定的函数条件,返回当前 Optional
,否则返回一个空的 Optaional
实例,可以用来过滤特殊值。
String name = "AiJiangnan";// 给定的条件是字符串包含Ai String result = Optional.of(name).filter(str -> str.contains("Ai")).orElse("字符串不包含Ai"); System.out.println(result);
如果有值存在,可以将该值的类型转换成其他类型,并返回转换后类型的 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。
如果有值存在,可以将该值的类型转换成其他类型,但最终只能转成 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);