通过Java三元运算符的自动装箱行为实现NullPointerException

通过Java三元运算符的自动装箱行为实现NullPointerException,java,nullpointerexception,autoboxing,ternary,Java,Nullpointerexception,Autoboxing,Ternary,前几天,我遇到了一个非常奇怪的NullPointerException,它是由三元运算符中的意外类型转换引起的。鉴于此(示例性)功能: Integer getNumber() { return null; } 我希望编译后,以下两个代码段完全相同: Integer number; if (condition) { number = getNumber(); } else { number = 0; } vs 结果表明,如果条件为真,则if-语句工作正常,而第二个代码

前几天,我遇到了一个非常奇怪的
NullPointerException
,它是由三元运算符中的意外类型转换引起的。鉴于此(示例性)功能:

Integer getNumber() {
    return null;
}
我希望编译后,以下两个代码段完全相同:

Integer number;
if (condition) {
    number = getNumber();
} else {
    number = 0;
}
vs

结果表明,如果
条件
,则
if
-语句工作正常,而第二个代码段中的三元运算抛出一个
NullPointerException
。似乎三元操作已决定将两个选项类型强制转换为
int
,然后再将结果自动装箱为
整数
!?!事实上,如果我显式地将
0
转换为
Integer
,异常就会消失。换言之:

Integer number = (condition) ? getNumber() : 0;
不同于:

Integer number = (condition) ? getNumber() : (Integer) 0;

因此,三元运算符和等价的
if-else
-语句之间似乎存在字节码差异(这是我没有预料到的)。这就引出了三个问题:为什么会有差异?这是三元实现中的错误还是类型转换的原因?考虑到存在差异,三元运算的性能是否比等价的
if
-语句差多少(我知道,差异可能不会很大,但仍然如此)?

问题在于:

Integer number = (condition) ? getNumber() : 0;
强制取消装箱和重新装箱getNumber()的结果。这是因为三元(0)的false部分是一个整数,因此它会尝试将getNumber()的结果转换为int。而以下情况不会:

Integer number = (condition) ? getNumber() : (Integer) 0;

这不是一个bug,只是Java选择的做事方式。

这就是它应该如何工作的。三元运算符并不等同于常规的
if
语句。
if
else
的主体是语句,而
后面的部分是表达式,需要计算为相同类型

换句话说:
a=b?如果(b)a=c,则d
不应等同于
;否则a=d。相反,
b?c:d本身就是一个表达式,将其结果赋值给a不会影响结果。

根据:-

条件表达式的类型确定如下:

  • 如果第二个操作数和第三个操作数具有相同的类型(可能是null类型),则这就是条件类型 表情
  • 如果第二个和第三个操作数中的一个是基元类型T,而另一个的类型是应用装箱转换的结果
    (§5.1.7)至T,则条件表达式的类型为T

您真的认为三元运算符中存在缺陷,而不是您对该运算符的使用和限制的理解?你认为这种现实发生的可能性有多大?考虑将问题的标题改为“三元运算符如何工作的误解”。这就是为什么我要问编译器为什么决定GET NUMLUBE()和0应该把结果都赋值给int,如果我把结果分配给一个整数。对我来说,在比较之前将这两个参数转换为这两种类型中限制性更强的类型,而不是在比较之后转换为实际需要的类型是完全没有意义的。为什么要先取消绑定,然后再重新绑定getNumber()?这与您或我认为应该发生的事情没有关系。更重要的是JLS中清楚地记录了什么。我从标题和标签中删除了“bug”-请尝试写一个简洁的相关标题。@pst:很好。我再次稍微更改了标题和标签,以便在其他人遇到此问题并尝试用谷歌搜索时更容易找到此线程。这对于调试来说是一个非常头痛的问题,尤其是当“null”情况很少发生时。这也是我认为这可能是一个错误的部分原因,因为它使Java产生不太稳定的代码(对我来说)没有明显的原因。@Markus看到
如果第二个和第三个操作数中的一个是基元类型T,而另一个的类型是对T应用装箱转换(§5.1.7)的结果,那么条件表达式的类型是T.
,所以,应该是这样的。留下一个问题,即两者之间是否存在性能差异,特别是考虑到所有自动(非)装箱的情况。@Markus。。嗯,你不能说是否有性能差异。。这无疑给了程序员编写代码的便利性。。但是是的,因为这只包括了
拆箱的一小部分。。如果不是这样的话。。因此,性能可能会很低。@Markus。。但鉴于三元运算符主要用于
单个
语句条件。。因此,这不太令人担忧。。当然,您将无法将整个表达式从if-else块移动到三元运算符。。所以,两者都有利弊。。
Integer number = (condition) ? getNumber() : (Integer) 0;