允许使用三元运算符返回 null 作为 int,但不允许使用 if 语句

让我们看看下面的代码片段中的简单 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 代码中,即使函数的返回类型是 inttemp()方法也不会发生编译器错误,并且我们尝试返回值 null(通过语句 return true ? null : 0;)。编译时,这显然会导致运行时异常 NullPointerException

但是,如果我们使用 if语句(如在 same()方法中)表示三元操作符,那么看起来同样是错误的,是的会发出编译时错误!为什么?

17005 次浏览

我认为,Java 编译器将 true ? null : 0解释为一个 Integer表达式,这个表达式可以隐式转换为 int,从而可能产生 NullPointerException

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

编译器将 null解释为对 Integer的空引用,对条件运算符应用自动装箱/取消装箱规则(如 Java 语言规范,15.25所述) ,然后愉快地继续前进。这将在运行时生成一个 NullPointerException,您可以通过尝试进行确认。

事实上,这一切都在 Java 语言规范中得到了解释。

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

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

因此,(true ? null : 0)中的“ null”获得一个 int 类型,然后自动装箱为 Integer。

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

if语句的情况下,null引用不被视为 Integer引用,因为它没有参与强制将其解释为 表情表情。因此,这个错误可以很容易地在编译时被捕获,因为它更清楚地是一个 类型错误。

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

  • 如果第二个和第三个操作数具有相同的类型(可能是 null Type) ,那么这就是条件表达式的类型。 < br/> < br/> 不适用,因为 null不是 int

  • 如果第二个和第三个操作数之一的类型为 boolean,并且 另一个是 Boolean 类型,那么条件表达式的类型是 Boolean。 < br/> < br/> 不适用,因为 nullint都不是 booleanBoolean

  • 如果第二个和第三个操作数之一的空类型和 Other 是一个引用类型,那么条件表达式的类型是 引用类型。 < br/> < br/> 不适用,因为 null是空类型,但 int不是引用类型。

  • 否则,如果第二个和第三个操作数具有可转换的类型 (5.1.8)到数字类型,然后有几种情况: [ ... ] < br/> < br/> 应用: null被视为可转换为数值类型,并在5.1.8“解除装箱转换”中定义以抛出 NullPointerException

首先要记住的是,Java 三元运算符有一个“ type”,这是编译器将确定和考虑的,不管第二个或第三个参数的实际/实际类型是什么。取决于几个因素,三元操作符类型可以通过不同的方式确定,如 Java 语言规格15.26所示

在上面的问题中,我们应该考虑最后一种情况:

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

这是迄今为止最复杂的情况下,一旦你看看 应用捕获转换(5.1.10),最重要的是在 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳

简单地说,经过极度简化后,我们可以将这个过程描述为计算第二个和第三个参数的“最小公共超类”(是的,想想 LCM)。这将给出三元运算符“ type”。同样,我刚才说的是一个极端的简化(考虑实现多个公共接口的类)。

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

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标记它。

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

更正:

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

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

private int temp() {


if (true) {
Integer x = null;
return x;// since that is fine because of unboxing 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
}

这样吧:

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);


}


}

输出为 true,true。

Eclipse 颜色将条件表达式中的1编码为 autobox。

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