背景
在笔者工作中曾经遇到过一次NPE异常,导致此异常的原因查到最后发现是三目运算符自动装箱导致的,我们来分析一下这个case。
首先我们来还原一下当时大致是怎样一个case。
public class NpeTest {
private Boolean bool;
public Boolean getBool() {
return bool;
}
public void setBool(Boolean bool) {
this.bool = bool;
}
public static void main(String[] args) {
NpeTest npeTest = new NpeTest();
npeTest.setBool(null);
Boolean test = npeTest != null ? npeTest.getBool() : false;
System.out.println(test);
}
}
如上述代码,当时大概是写了一个类似的三目运算符逻辑,然后在这里发生了NPE空指针异常。
分析
在jls-15.25标准中有如下定义:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
我们来反编译最上面的代码看一下。
首先在iterm终端进入目标类路径,输入以下命令编译与反编译NpeTest类。
javac NpeTest.java
javap -c NpeTest
最后输出信息如下:
public class org.springside.modules.utils.NpeTest {
public org.springside.modules.utils.NpeTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Boolean getBool();
Code:
0: aload_0
1: getfield #2 // Field bool:Ljava/lang/Boolean;
4: areturn
public void setBool(java.lang.Boolean);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field bool:Ljava/lang/Boolean;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class org/springside/modules/utils/NpeTest
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: aconst_null
10: invokevirtual #5 // Method setBool:(Ljava/lang/Boolean;)V
13: aload_1
14: ifnull 27
17: aload_1
18: invokevirtual #6 // Method getBool:()Ljava/lang/Boolean;
21: invokevirtual #7 // Method java/lang/Boolean.booleanValue:()Z
24: goto 28
27: iconst_0
28: invokestatic #8 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
31: astore_2
32: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
35: aload_2
36: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
39: return
}
可以看到21后面跟的是booleanValue
所以基本可以确定是一个拆箱操作了
于是我们可以确定是因为三目运算符中,第二位是Boolean包装类型,第三位是boolean基本数据类型,所以进行了拆箱。
那么我们改一下再来看一下
public class NpeTest {
private Boolean bool;
public Boolean getBool() {
return bool;
}
public void setBool(Boolean bool) {
this.bool = bool;
}
public static void main(String[] args) {
NpeTest npeTest = new NpeTest();
npeTest.setBool(null);
Boolean test = npeTest != null ? npeTest.getBool() : Boolean.FALSE;
System.out.println(test);
}
}
重新执行一下javac和javap
public class org.springside.modules.utils.NpeTest {
public org.springside.modules.utils.NpeTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.Boolean getBool();
Code:
0: aload_0
1: getfield #2 // Field bool:Ljava/lang/Boolean;
4: areturn
public void setBool(java.lang.Boolean);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field bool:Ljava/lang/Boolean;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class org/springside/modules/utils/NpeTest
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: aconst_null
10: invokevirtual #5 // Method setBool:(Ljava/lang/Boolean;)V
13: aload_1
14: ifnull 24
17: aload_1
18: invokevirtual #6 // Method getBool:()Ljava/lang/Boolean;
21: goto 27
24: getstatic #7 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean;
27: astore_2
28: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_2
32: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
35: return
}
可以看到booleanValue没有了,问题解决。
网友评论