[英]Returning null as an int permitted with ternary operator but not if statement
让我们在以下片段中查看简单的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代码中,即使函数的返回类型为int
, temp()
方法也不会发出编译器错误,并且我们试图返回值null
(通过语句return true ? null : 0;
)。 编译后,这显然会导致运行时异常NullPointerException
。
但是,如果我们用if
语句(如same()
方法中)表示三元运算符,那似乎是错误的,这确实会产生编译时错误! 为什么?
编译器将null
解释为对Integer
的null引用,为条件运算符应用自动装箱/拆箱规则(如Java语言规范15.25中所述 ),并愉快地前进。 这将在运行时生成NullPointerException
,您可以通过尝试进行确认。
我认为,Java编译器解释为true ? null : 0
true ? null : 0
作为Integer
表达式,可以隐式转换为int
,可能会给出NullPointerException
。
对于第二种情况, null
表达式具有特殊的null类型, 请参见 ,因此代码return null
会使类型不匹配。
实际上,所有这些都在Java Language Specification中进行了说明。
条件表达式的类型确定如下:
- 如果第二个操作数和第三个操作数具有相同的类型(可能是null类型),则这是条件表达式的类型。
因此,您的(true ? null : 0)
的“ null”将得到一个int类型,然后自动装箱为Integer。
尝试使用类似的方法进行验证(true ? null : null)
,您将得到编译器错误。
在if
语句的情况下, null
引用不被视为Integer
引用,因为它没有参与强制将其解释为这样的表达式 。 因此,错误很容易在编译时捕获,因为它显然是类型错误。
至于条件运算符,请参见Java语言规范§15.25“条件运算符? :
? :
”很好地回答了如何应用类型转换的规则:
- 如果第二个操作数和第三个操作数具有相同的类型(可能是null类型),则这是条件表达式的类型。
不适用,因为null
不是int
。
- 如果第二个操作数和第三个操作数之一为布尔型,而另一个的类型为布尔型,则条件表达式的类型为布尔型。
不适用,因为null
和int
都不是boolean
或Boolean
。
- 如果第二个操作数和第三个操作数之一为空类型,而另一个操作数的类型为引用类型,则条件表达式的类型为该引用类型。
不适用,因为null
是null类型,但int
不是引用类型。
- 否则,如果第二和第三操作数的类型可以转换(第5.1.8节)为数字类型,则有以下几种情况:[…]
适用:将null
视为可转换为数字类型,并在§5.1.8“拆箱转换”中定义以抛出NullPointerException
。
首先要记住的是Java三元运算符有一个“类型”,这是编译器将确定并考虑的内容,而不管第二或第三参数的实际/实际类型是什么。 根据几种因素,以不同的方式确定三元运算符的类型,如Java语言规范15.26中所示。
在上面的问题中,我们应该考虑最后一种情况:
否则,第二和第三操作数分别为S1和S2类型。 令T1为对S1进行装箱转换所产生的类型,而T2为对S2进行装箱转换所产生的类型。 条件表达式的类型是将捕获转换(§5.1.10)应用于lub(T1,T2) (§15.12.2.7)的结果。
一旦您了解了如何应用捕获转换(第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
可以自动装箱为任何东西,因此“ Least Common超类”是Integer
类,这将是上面条件表达式(三元运算符)的返回类型。 然后,返回值将是Integer
类型的空指针,即三元运算符将返回的值。
在运行时,当Java虚拟机将Integer
拆箱时,将引发NullPointerException
。 发生这种情况是因为JVM尝试调用函数null.intValue()
,其中null
是自动装箱的结果。
在我看来(由于我的意见不在Java语言规范中,所以无论如何人们都会发现它是错误的),编译器在评估问题中的表达式方面做得很差。 既然你写的是true ? param1 : param2
true ? param1 : param2
编译器应该马上确定第一个参数- null
-将被退回,它应该产生一个编译器错误。 这有点类似于您编写while(true){} etc...
并且编译器抱怨循环下面的代码并使用Unreachable Statements
对其进行标记。
您的第二种情况非常简单,这个答案已经太长了...;)
更正:
经过另一次分析,我认为我可以错将null
值装箱/自动装箱到任何东西。 谈论类Integer时,显式装箱包括调用new Integer(...)
构造函数,或者调用Integer.valueOf(int i);
(我在某个地方找到了此版本)。 前者将抛出NumberFormatException
(并且不会发生),而第二个则将变得毫无意义,因为int
不能为null
。
实际上,由于编译器知道,在第一种情况下可以对表达式进行求值,因为编译器知道必须将其作为Integer
进行求值,但是在第二种情况下,无法确定返回值的类型( null
),因此无法确定编译。 如果将其Integer
为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标记为自动装箱。
我的猜测是编译器将表达式的返回类型视为Object。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.