Java三元运算符似乎不一致地将整数转换为整数
我的一个学生在使用三元运算符时遇到空指针异常,这有时会导致空指针。我想我理解这个问题,但它似乎是由不一致的类型推断造成的。或者换句话说,我觉得这里的语义不一致,错误应该可以避免,而不改变他的方法 这个问题类似于,但不同于。在这个问题中,必须将空整数强制为int,因为函数的返回值是int。但是,在我的学生代码中不是这样 此代码运行良好:Java三元运算符似乎不一致地将整数转换为整数,java,casting,Java,Casting,我的一个学生在使用三元运算符时遇到空指针异常,这有时会导致空指针。我想我理解这个问题,但它似乎是由不一致的类型推断造成的。或者换句话说,我觉得这里的语义不一致,错误应该可以避免,而不改变他的方法 这个问题类似于,但不同于。在这个问题中,必须将空整数强制为int,因为函数的返回值是int。但是,在我的学生代码中不是这样 此代码运行良好: Integer x = (5>7) ? 3 : null; x的值为空。没有NPE。在这种情况下,编译器可以计算出三元运算符的结果需要是整数,因此它将3(
Integer x = (5>7) ? 3 : null;
x的值为空。没有NPE。在这种情况下,编译器可以计算出三元运算符的结果需要是整数,因此它将3(一个int)转换为整数,而不是将null转换为int
但是,运行此代码:
Integer x = (5>7) ? 3 : (5 > 8) ? 4 : null;
结果是NPE。发生这种情况的唯一原因是null被强制转换为int,但这并不是必需的,而且似乎与代码的第一位不一致。也就是说,如果编译器可以为第一个snipet推断出三元运算符的结果是一个整数,那么为什么在第二种情况下它不能这样做呢?第二个三元表达式的结果必须是整数,因为该结果是第一个三元运算符的第二个结果,所以第一个三元运算符的结果也应该是整数
另一个snipet很好用:
Integer three = 3;
Integer x = (5>7) ? three : (5 > 8) ? three+1 : null;
在这里,编译器似乎能够推断出两个三元运算符的结果都是整数,因此不会强制将null强制转换为int。关键是条件运算符是右关联的。确定条件表达式的结果类型的规则如下所示,但归结起来如下:
(5>8)?4:计算null
,第二个操作数是int
,第三个操作数是null
,如果我们在表中查找,此表达式的结果类型是整数。(换句话说:由于其中一个操作数为null
,因此将其视为引用条件表达式)
(5>7)?3:
求值,这意味着在上面链接的表中,我们需要查找第二个操作数int
和第三个操作数Integer
:它是int
。这意味着,
需要解除绑定,并使用NPE
失败(5>7)了吗?3:空
,如我们所见,如果第二个操作数为int
,第三个操作数为null
,则结果类型为Integer
。但我们将其分配给Integer
类型的变量,因此不需要取消装箱
但是,这仅发生在null
文本中,以下代码仍将抛出NPE,因为操作数类型int
和Integer
导致数值条件表达式:
Integer i = null;
Integer x = (5>7) ? 3 : i;
总而言之:它有一种逻辑,但它不是人类的逻辑
整数
,则结果是一个整数
int
,另一个类型为Integer
,则结果为int
null
引用),则结果为整数
Integer x = (5>7) ? 3 : (5 > 8) ? 4 : null;
看起来像这样:
Integer x = (Integer) ((5>7) ? (int) 3 : (5 > 8) ? 4 : null);
正如您所看到的,null再次被强制转换为int,这将失败
要解决这个问题,我们可以这样做:
Integer x = (Integer) ((5>7) ? (Integer) 3 : (5 > 8) ? 4 : null);
现在它不尝试将null转换为int,而是整数。在Java8之前,几乎所有情况下†,表达式的类型都是自下而上构建的,完全取决于子表达式的类型;它不依赖于上下文。这是好的和简单的,代码很容易理解;例如,重载解析取决于参数的类型,参数的解析独立于方法调用上下文 (†我所知道的唯一例外是) 给定
?int:Integer
形式的条件表达式,规范需要为其定义固定类型,而不考虑上下文。选择了int
类型,这在大多数用例中可能更好。
当然,它也是拆箱产生NPE的来源
在Java8中,上下文类型信息可用于类型推断。这对许多情况都是方便的;但它也会带来混淆,因为解析表达式的类型可能有两个方向。幸运的是,有些表达式仍然是独立的;它们的类型与上下文无关 w、 r.t条件表达式,我们不希望像
false?0:1
这样的简单表达式依赖于上下文;他们的类型不言而喻。另一方面,我们确实希望对更复杂的条件表达式进行上下文类型推断,例如false?f():g()
其中f/g()
需要类型推断
在基元类型和引用类型之间画了一条线。在op1?op2:op3
中,如果op2
和op3
都是“清晰”的基本类型(或装箱版本),则将其视为独立的。丹·史密斯-
我们在这里对条件表达式进行分类,以增强引用条件(15.25.3)的类型规则,同时保留布尔和数值条件的现有行为。如果我们试图统一处理所有条件,就会出现各种不必要的不兼容更改,包括重载解析和装箱/拆箱行为的更改
就你而言
Integer x = false ? 3 : false ? 4 : null;
由于false?4:null
是一个“明确的”(?)整数,因此父表达式的形式为?:int:Integer
;这是一个原始案例,其行为保持不变
Integer x = false ? 3 : false ? 4 : null;
static <T> T f1(){ return null; }
--
Integer x = false ? 3 : false ? f1() : null;
Integer x = false ? 3 : false ? Test.<Integer>f1() : null;
class MyList1 extends ArrayList<Integer>
{
//inherit public Integer get(int index)
}
class MyList2 extends ArrayList<Integer>
{
@Override public Integer get(int index)
{
return super.get(0);
}
}
MyList1 myList1 = new MyList1();
MyList2 myList2 = new MyList2();
Integer x1 = false ? 3 : false ? myList1.get(0) : null; // no NPE
Integer x2 = false ? 3 : false ? myList2.get(0) : null; // NPE !!!