自动装箱/拆箱可能导致NPE
在一次使用redisTemplate去操作的时候,发现IDEA提示UnBoxing of 'redisTemplate.hasKey(key)' may produce 'NullPointerException'
,意思是说自动拆箱可能会引发空指针异常。
因为hasKey
这个方法是返回Boolan类型,而方法的返回值是boolean,直接把Boolean的值返回,就会发生自动拆箱。
自动装箱与自动拆箱
基本数据类型的自动装箱(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
这就是一个自动装箱。
出现空指针的情况
- 包装器类型赋值给基本类型时,自动拆箱下出现的空指针
- 当把基本类型作为方法的参数类型,但是方法调用者传参使用的却是包装器类型,所以出现了自动拆箱的情况,此时包装器类型如果传的是NULL,方法中就很有可能出现NULL指针异常
例子
1
2
3
4
5
6
7
8
9
10
|
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反编译之后,查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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
1
2
3
|
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
|
总结
- 包装器类型和基本数据类型都可以的业务场景下,优先考虑使用基本类型
- 对于不确定的包装器类型,一定要对NULL情况做检验和判断