likes
comments
collection
share

Java包装类型自动装箱/拆箱导致NPE

作者站长头像
站长
· 阅读数 14

自动装箱/拆箱可能导致NPE

在一次使用redisTemplate去操作的时候,发现IDEA提示UnBoxing of 'redisTemplate.hasKey(key)' may produce 'NullPointerException',意思是说自动拆箱可能会引发空指针异常。

因为hasKey这个方法是返回Boolan类型,而方法的返回值是boolean,直接把Boolean的值返回,就会发生自动拆箱。

Java包装类型自动装箱/拆箱导致NPE

自动装箱与自动拆箱

基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。 一般我们要创建一个类的对象实例的时候,我们会这样: Class a = new Class(parameters); 当我们创建一个Integer对象时,却可以这样: Integer i = 100;(注意:和 int i = 100;是有区别的 ) 实际上,执行上面那句代码的时候,系统为我们执行了: Integer i = Integer.valueOf(100)。Integer i = 100这就是一个自动装箱。

出现空指针的情况

  1. 包装器类型赋值给基本类型时,自动拆箱下出现的空指针
  2. 当把基本类型作为方法的参数类型,但是方法调用者传参使用的却是包装器类型,所以出现了自动拆箱的情况,此时包装器类型如果传的是NULL,方法中就很有可能出现NULL指针异常

例子

public static void main(String[] args) {
	Map<String, Boolean> map = new HashMap<>(16);
	map.put("key1", true);
	map.put("key2", false);
	map.put("key3", false);
	Boolean b = ( map != null ? map.get("key") : false);
	System.out.println(b);
}
执行结果:
null

map不为空,当get()之后key不存在,Boolean类型的b被赋了空值。再看回redisTemplate.hasKey(key)的操作可能会产生空值,然后拆箱返回,因为这是一个工具类里的方法,调用者可能会直接调用,这时候就有了空指针异常的风险,影响了程序的健壮性了。

使用javap.exe反编译之后,查看

public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/HashMap
       3: dup
       4: bipush        16
       6: invokespecial #3                  // Method java/util/HashMap."<init>":(I)V
       9: astore_1
      10: aload_1
      11: ldc           #4                  // String key1
      13: iconst_1
      14: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
      17: invokeinterface #6,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      22: pop
      23: aload_1
      24: ldc           #7                  // String key2
      26: iconst_0
      27: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
      30: invokeinterface #6,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      35: pop
      36: aload_1
      37: ldc           #8                  // String key3
      39: iconst_0
      40: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
      43: invokeinterface #6,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      48: pop
      49: aload_1
      50: ifnull        67
      53: aload_1
      54: ldc           #9                  // String key
      56: invokeinterface #10,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
      61: checkcast     #11                 // class java/lang/Boolean
      64: goto          71
      67: iconst_0
      68: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
      71: astore_2
      72: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      75: aload_2
      76: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      79: return

从反编译的结果可以看到Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;出现了四次,三次是map.put()的时候,最后一次是:Boolean b = ( map != null ? map.get("key") : false);可以看出Boolean类型自动装箱valueOf()。如果一个Boolean引用为空时,在自动拆箱就是一个空值。这就会发生NPE

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

总结

  1. 包装器类型和基本数据类型都可以的业务场景下,优先考虑使用基本类型
  2. 对于不确定的包装器类型,一定要对NULL情况做检验和判断