虽然已有几年开发经验,但是工作中发现,这程序中的坑啊,踩的也真是不亦乐乎,很是惭愧。
即使是最最基础的Java语言本身的特性,也有点儿说不清道不明了。大家都曾努力学习过,都正在努力地工作,为什么一些错误总是一而再再而三的犯呢?我想这不是个人基础的问题,而是没有认真总结与回顾。
所以,即日起,小弟我会把工作上学习中遇到的问题都记录下来,可能有些问题会很白痴,然而不惧贻笑大方方能进步。
这第一个问题便是:
包装类型在条件运算中的使用不当造成的空指针异常。
我们曾在教科书上学到过,条件表达式
b ? x : y;
等价于
if (b) {
x;
} else {
y;
}
所以
int a = (b ? x : y);
等价于
int a;
if (b) {
a = x;
} else {
a = y;
}
接下来介绍我的发现。
//InnerClass的简化定义如下:
private class InnerClass {
private Long id;
Long getId() {
return this.id;
}
}
private static void testBoxedTypeWithIf() {
InnerClass innerClass = new InnerClass();
Long idValue;
if (flag) {
idValue = innerClass.getId();
} else {
idValue = 0L;
}
System.out.println(idValue);
}
flag的值为true,所以程序毫无疑问会打印出null。
根据if判断和条件运算的等价关系,我在程序中用了如下的方式实现逻辑:
private static void testBoxedTypeWithConditionalOperation() {
InnerClass innerClass = new InnerClass();
Long idValue = flag ? innerClass.getId() : 0L;
System.out.println(idValue);
}
如果使用一样的测试用例,肯定有人会认为这段程序也输出null,至少之前的我是这么认为的。这段程序运行的结果是抛出空指针异常,我们来看看testBoxedTypeWithConditionalOperation方法和testBoxedTypeWithIf方法的反编译代码:
private static void testBoxedTypeWithConditionalOperation() {
InnerClass innerClass = new InnerClass(null);
Long idValue = Long.valueOf(flag ? innerClass.getId().longValue() : 0L);
System.out.println(idValue);
}
private static void testBoxedTypeWithIf() {
InnerClass innerClass = new InnerClass(null);
Long idValue;
if (flag)
idValue = innerClass.getId();
else {
idValue = Long.valueOf(0L);
}
System.out.println(idValue);
}
可以看到,testBoxedTypeWithConditionalOperation中将条件运算中的包装类型解包装了,在testBoxedTypeWithIf中将原始类型包装了。
在我以往的认知中,包装类型与原始类型的自动包装与解包装的操作,就像testBoxedTypeWithIf中,是在这些变量之间有直接的比较、赋值或者计算(如加减乘除)的时候。然而在本例的条件运算中,即使看似没有直接的关系,编译器依然对包装类型做了解包装的操作。
所以,在包装类型与条件运算混用的时候,要么用if判断替代条件运算,要么在条件运算的判断条件中先对包装类型是否为空做判断,做法如下:
private static void testBoxedTypeWithCorrectConditionalOperation() {
InnerClass innerClass = new InnerClass();
Long idValue = flag && innerClass.getId() != null ? innerClass.getId() : 0L;
System.out.println(idValue);
}
对于本例中用的测试用例,即flag == true,testBoxedTypeWithCorrectConditionalOperation将输出0。
通过反编译代码发现,条件运算与if判断只是在程序的执行路径上保持一致,具体的执行细节,因不同编程语言而异。而今Java前端与后端编译器越来越聪明,对程序员来说不见得是一件好事儿。