携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
首先上代码:
public class A {
private Long age;
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
}
这是一个很普通的一个类,里面有一个Long型的age
属性。
再看一下业务代码:
public static void main(String[] args) {
A a = new A();
Long age = (a == null ? 0L : a.getAge());
System.out.println("age = " + age);
}
这个代码就是判断a对象是否为空,如果为空就返回0L,否则就取a对象的age属性值。这种代码应该来说在我们业务开发中会见到。但是...... 运行一下看结果:
当时我就奇怪了,a是判空了的呀,如果a为空怎么会报空指针异常呢?况且a在这里不为空,唯一为null的是a里面的age属性,此时我就怀疑是JVM搞的鬼。
通过对class文件反汇编看看:执行以下
javap -c
试试看:
public class com.example.demo.bugs.Ternary {
public com.example.demo.bugs.Ternary();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/example/demo/bugs/A
3: dup
4: invokespecial #3 // Method com/example/demo/bugs/A."<init>":()V
7: astore_1
8: aload_1
9: ifnonnull 16
12: lconst_0
13: goto 23
16: aload_1
17: invokevirtual #4 // Method com/example/demo/bugs/A.getAge:()Ljava/lang/Long;
20: invokevirtual #5 // Method java/lang/Long.longValue:()J
23: invokestatic #6 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
26: astore_2
27: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
30: new #8 // class java/lang/StringBuilder
33: dup
34: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
37: ldc #10 // String age =
39: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
42: aload_2
43: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
46: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
49: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
52: return
}
注意看字节码的20行号,发现了一个Long.valueOf
的优化指令,猜测JVM在对字面量形式的Java代码做了编译优化,三目运算里面自动优化成了Long.valueOf(a.getAge())
我们试一下这种写法会有什么结果:
public static void main(String[] args) {
A a = new A();
// Long age = (a == null ? 0L : a.getAge());
// System.out.println("age = " + age);
System.out.println("test" + Long.valueOf(a.getAge()));
}
果然,问题的原因找到了,是Jvm在对一些字面量形式的代码会做一些编译优化。
常见的优化比如Long.valueOf、Integer.valueOf、StringBuilder等等
最后解决方案就是:
public static void main(String[] args) {
A a = new A();
Long age = (a == null ? Long.valueOf(0L) : a.getAge());
System.out.println("age = " + age);
}
避免以字面量形式进行三目运算比较,否则JVM会做编译期动态优化。