Java三元运算符似乎不一致地将整数转换为整数

Java三元运算符似乎不一致地将整数转换为整数,java,casting,Java,Casting,我的一个学生在使用三元运算符时遇到空指针异常,这有时会导致空指针。我想我理解这个问题,但它似乎是由不一致的类型推断造成的。或者换句话说,我觉得这里的语义不一致,错误应该可以避免,而不改变他的方法 这个问题类似于,但不同于。在这个问题中,必须将空整数强制为int,因为函数的返回值是int。但是,在我的学生代码中不是这样 此代码运行良好: Integer x = (5>7) ? 3 : null; x的值为空。没有NPE。在这种情况下,编译器可以计算出三元运算符的结果需要是整数,因此它将3(

我的一个学生在使用三元运算符时遇到空指针异常,这有时会导致空指针。我想我理解这个问题,但它似乎是由不一致的类型推断造成的。或者换句话说,我觉得这里的语义不一致,错误应该可以避免,而不改变他的方法

这个问题类似于,但不同于。在这个问题中,必须将空整数强制为int,因为函数的返回值是int。但是,在我的学生代码中不是这样

此代码运行良好:

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
    引用),则结果为
    整数
  • 由于null被强制转换为int,因此导致NPE

    你的代码也一样

    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 !!!