NullPointerException在这里简称为NPE
通过一些实例总结下常见的NPE问题:
1. 自动拆箱抛NPE
实体类:
1 public class User { 2 private String name; 3 private Integer age; 4 5 public String getName() { 6 return name; 7 } 8 public void setName(String name) { 9 this.name = name; 10 } 11 public Integer getAge() { 12 return age; 13 } 14 public void setAge(Integer age) { 15 this.age = age; 16 } 17 @Override 18 public String toString() { 19 return "User [name=" + name + ", age=" + age + "]"; 20 } 21 }
测试类:
1 public class NPEDemo { 2 3 public static void main(String[] args) { 4 method(); 5 } 6 7 static int method(){ 8 User user = new User(); 9 System.out.println("---" + new User().toString()); 10 return user.getAge(); 11 } 12 }
出错:
---User [name=null, age=null] Exception in thread "main" java.lang.NullPointerException at finaldemo.NPEDemo.method(NPEDemo.java:12) at finaldemo.NPEDemo.main(NPEDemo.java:6)
分析错误原因:
包装类型为 null 时,进行自动转换为基本数据类型报错。
return user.getAge();
其中user不为空,但是user.getAge()为空(Integer类型)
而且method方法需要的返回类型是int,此时就自动进行了拆箱转换,即从Integer到int类型的转换。
而从Integer到int类型转换的时候user.getAge().intValue(); 会报空指针异常
解决方案:
返回之前进行判断与处理或者改为相同类型。
2. 级联调用易产生NPE
这段代码有点容易迷惑人,因为它进行了集合元素的 isEmpty
判断,按说不会出问题了吧。看代码:
1 static void method1() { 2 List<User> list = new ArrayList<User>(); 3 list.add(new User()); 4 5 if (!CollectionUtils.isEmpty(list)) { 6 for (User user : list) { 7 System.out.println("userid:" + user.getAge().toString()); 8 } 9 } 10 }
报错:
Exception in thread "main" java.lang.NullPointerException at finaldemo.NPEDemo.method1(NPEDemo.java:18) at finaldemo.NPEDemo.main(NPEDemo.java:9)
分析错误原因:
其实就是尽管你在之前做了对象不为空的判断,但你并不能保证对象中的值不为空,而且这时候去级联调用就会抛 NPE 。
手册中关于 NPE 的描述:
防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。 集合里的元素即使 isEmpty,取出的数据元素也可能为 null。 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE
3. 关于 Equals
这是日常开发中用于相等比较使用最多的方法了吧,因为当年谁没被 ==
坑过阿。现在一般我们都会这么写:
1 public class NPEDemo { 2 3 public static void main(String[] args) { 4 method1(); 5 } 6 7 static void method1() { 8 User user = new User(); 9 user.getName().equals("men"); 10 } 11 }
分析错误原因:
一不小心使用了 null 值调用了 Equals
方法。
解决方案:
很简单咯,这么写:"mafly".equals(user.getName());
equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
4.Map 下的 NPEMap
应该是我们开发中使用最频繁的了,最常用的可能有 HashMap
、ConcurrentHashMap
这俩了,可能会一不留神写出这样的代码:
1 public class NPEDemo { 2 3 public static void main(String[] args) { 4 method1(); 5 } 6 7 static void method1() { 8 User user = new User(); 9 Map<Integer, Object> map = new HashMap<Integer, Object>(); 10 map.put(user.getAge(), user.getName()); 11 // System.out.println("---" + map); 12 Map<Integer, Object> map2 = new ConcurrentHashMap<Integer, Object>(); 13 map2.put(user.getAge(), user.getName()); 14 15 } 16 }
报错:
Exception in thread "main" java.lang.NullPointerException at java.util.concurrent.ConcurrentHashMap.put(Unknown Source) at finaldemo.NPEDemo.method1(NPEDemo.java:21) at finaldemo.NPEDemo.main(NPEDemo.java:12)
分析错误原因:
可能我们知道 ConcurrentHashMap
的 K/V 都不能为空,但我们有时候并不知道传进来的值是否为空。
解决方案:
设置时做下检验,对它的特性正确理解及使用。
由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储 null 值时会抛出 NPE 异常
Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类 | Key | Value | Super | 说明 |
---|---|---|---|---|
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 分段锁技术 |
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |