将null返回为允许使用三元运算符的int,而不是if语句

时间:2011-11-11 19:30:39

标签: java nullpointerexception conditional-operator autoboxing

让我们看看以下代码段中的简单Java代码:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

在这个最简单的Java代码中,即使函数的返回类型为temp()int方法也不会发出编译器错误,我们正在尝试返回值null(通过声明return true ? null : 0;)。编译时,这显然会导致运行时异常NullPointerException

但是,如果我们使用if语句(如same()方法)表示三元运算符, 发出,则表明同样的错误编译时错误!为什么呢?

8 个答案:

答案 0 :(得分:115)

编译器将null解释为对Integer的空引用,对条件运算符应用自动装箱/取消装箱规则(如Java Language Specification, 15.25中所述),并快乐地开始。这将在运行时生成NullPointerException,您可以通过尝试来确认。

答案 1 :(得分:39)

我认为,Java编译器将true ? null : 0解释为Integer表达式,可以隐式转换为int,可能会给NullPointerException

对于第二种情况,表达式null属于特殊的 null类型 see,因此代码return null使类型不匹配。

答案 2 :(得分:32)

实际上,这一切都在Java Language Specification中进行了解释。

  

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

     
      
  • 如果第二个和第三个操作数具有相同的类型(可能是null类型),那么这就是条件表达式的类型。
  •   

因此" null"在(true ? null : 0)中获取int类型,然后自动装箱到Integer。

尝试这样的方法来验证这个(true ? null : null),你会得到编译错误。

答案 3 :(得分:25)

对于if语句,null引用不会被视为Integer引用,因为它没有参与表达式它被解释为这样。因此,错误可以在编译时轻松捕获,因为它更明显是类型错误。

对于条件运算符,Java语言规范§15.25“条件运算符? :”在关于如何应用类型转换的规则中很好地回答了这个问题:

  
      
  • 如果第二个和第三个操作数具有相同的类型(可能为null)   type),那就是条件表达式的类型。

      不适用,因为null不是int
  •   
     
     
      
  • 如果第二个和第三个操作数之一是boolean类型和类型   other是Boolean类型,然后条件表达式的类型是boolean。

      不适用,因为nullint都不是booleanBoolean
  •   
     
     
      
  • 如果第二个和第三个操作数之一是null类型和类型   other是引用类型,那么条件表达式的类型就是那个   参考类型。

      不适用,因为null属于null类型,但int不是引用类型。
  •   
     
     
      
  • 否则,如果第二个和第三个操作数具有可转换的类型   (§5.1.8)对于数字类型,则有几种情况:[...]

      适用:null被视为可转换为数字类型,并在§5.1.8“取消装箱转换”中定义,以抛出NullPointerException
  •   

答案 4 :(得分:11)

要记住的第一件事是Java三元运算符具有“类型”,这就是编译器将确定和考虑的内容,无论第二个或第三个参数的实际/实际类型是什么。根据几个因素,三元运算符类型以不同的方式确定,如Java Language Specification 15.26

中所示

在上面的问题中,我们应该考虑最后一个案例:

  

否则,第二个和第三个操作数分别是 S1 S2 类型。让 T1 成为应用装箱转换到 S1 的结果类型,让 T2 成为将装箱转换应用于 S2 。条件表达式的类型是将捕获转换(第5.1.10节)应用于 lub(T1,T2)(第15.12.2.7节)的结果。

这是迄今为止最复杂的案例,一看到applying capture conversion (§5.1.10),最重要的是 lub(T1, T2)

用简单的英语和极端简化后,我们可以将过程描述为计算第二和第三参数的“最小公共超类”(是的,考虑LCM)。这将为我们提供三元运算符“类型”。同样,我刚才所说的是极端简化(考虑实现多个通用接口的类)。

例如,如果您尝试以下操作:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

您会注意到条件表达式的结果类型为java.util.Date,因为它是Timestamp / Time对的“最小公共超类”。

由于null可以自动生成任何内容,因此“最小公共超类”是Integer类,这将是上面条件表达式(三元运算符)的返回类型。返回值将是类型为Integer的空指针,这将是三元运算符返回的值。

在运行时,当Java虚拟机取消装箱时,Integer会抛出NullPointerException。发生这种情况是因为JVM尝试调用函数null.intValue(),其中null是自动装箱的结果。

在我看来(并且由于我的观点不在Java语言规范中,很多人会发现它错了)编译器在评估你的问题中的表达式方面表现不佳。鉴于您编写了true ? param1 : param2,编译器应立即确定将返回第一个参数 - null - 并且应该生成编译器错误。这有点类似于编写while(true){} etc...时编译器抱怨循环下面的代码并用Unreachable Statements标记它。

你的第二个案子很简单,这个答案已经太长了......;)

<强> CORRECTION:

经过另一次分析后,我认为我错误地说null值可以装箱/自动装箱。谈到类Integer,显式装箱包括调用new Integer(...)构造函数或者Integer.valueOf(int i);(我在某个地方找到了这个版本)。前者会抛出NumberFormatException(这不会发生),而第二种情况则没有意义,因为int不能null ...

答案 5 :(得分:4)

实际上,在第一种情况下,可以评估表达式,因为编译器知道它必须被评估为Integer,但在第二种情况下,返回值的类型(null )无法确定,因此无法编译。如果将其强制转换为Integer,则代码将被编译。

答案 6 :(得分:2)

private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of auto-boxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}

答案 7 :(得分:0)

这个怎么样:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

输出为真,真实。

Eclipse颜色将条件表达式中的1编码为自动装箱。

我的猜测是编译器将表达式的返回类型视为Object。