让我们看看以下代码段中的简单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()
方法)表示三元运算符, 发出,则表明同样的错误编译时错误!为什么呢?
答案 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。
不适用,因为null
和int
都不是boolean
或Boolean
。
- 如果第二个和第三个操作数之一是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。