Java中的Tricky三元运算符 – 自动装箱

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

然而,如果我们使用if语句(如同same()方法)表示三元运算符,那么看起来同样的事情是错误的,这产生编译时错误! 为什么?

编译器将null解释为对Integer的空引用,对条件运算符应用自动装箱/拆箱规则(如Java语言规范15.25中所述 ),并愉快地移动。 这会在运行时产生一个NullPointerException ,你可以通过尝试来确认。

我认为,Java编译器解释为true ? null : 0 true ? null : 0作为Integerexpression式,可以隐式转换为int ,可能会给出NullPointerException

对于第二种情况,expression式null是特殊的空types ,所以代码return null使得types不匹配。

实际上,它们都是在Java语言规范中解释的。

条件expression式的types如下确定:

  • 如果第二个和第三个操作数具有相同的types(可能是空types),那么这就是条件expression式的types。

因此你的(true ? null : 0)的“null”得到一个inttypes,然后被自动装箱到Integer。

尝试这样的事情来validation这个(true ? null : null) ,你会得到编译器错误。

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

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

  • 如果第二个和第三个操作数具有相同的types(可能是空types),那么这就是条件expression式的types。

    不适用,因为null不是int


  • 如果第二个和第三个操作数中的一个是布尔types,而另一个的types是布尔types,则条件expression式的types是布尔值。

    不适用,因为既不null也不intbooleanBoolean


  • 如果第二个和第三个操作数中的一个是空types,而另一个的types是引用types,那么条件expression式的types就是该引用types。

    不适用,因为null是空types,但是int不是引用types。


  • 否则,如果第二个和第三个操作数的types可以转换(§5.1.8)为数字types,那么有几种情况:[…]

    应用: null被视为可转换为数字types,并在§5.1.8“取消装箱转换”中定义,以引发NullPointerException

首先要记住的是,Java三元运算符有一个“types”,这就是编译器决定和考虑的内容,而不pipe第二个或第三个参数的实际types是什么。 根据几个因素,三元运算符types以Java语言规范15.26中所示的不同方式确定

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

否则,第二个和第三个操作数分别是S1S2的types。 假设T1是将装箱转换为S1所得到的types,并设T2是将装箱转换为S2所得到的types。 条件expression式的types是将采集转换(§5.1.10)应用于lub(T1,T2) (§15.12.2.7)的结果。

这是迄今为止最复杂的情​​况,一旦你看看应用捕获转换(§5.1.10) ,最重要的是在lub(T1,T2)

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

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

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

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

由于null可以自动复制到任何东西,所以“最小公共超类”是Integer类,这将是上面的条件expression式(三元运算符)的返回types。 返回值将是一个Integertypes的空指针,三元运算符将会返回这个值。

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

在我看来(因为我的意见不在Java语言规范中,很多人会发现它是错误的)编译器在评估expression式的时候做得不好。 鉴于你写的是true ? param1 : param2 true ? param1 : param2编译器应该立即确定将返回第一个参数 – null – 它应该会产生一个编译器错误。 这有些类似于你编写while(true){} etc... ,编译器会抱怨循环下面的代码,并用Unreachable Statements标记它。

你的第二种情况是相当简单的,这个答案已经太长了;;)

更正:

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

实际上,在第一种情况下,可以评估expression式,因为编译器知道它必须被评估为Integer ,但是在第二种情况下返回值( null )的types不能被确定,所以它不能被编译。 如果将其转换为Integer ,则代码将被编译。

 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 } 

这个怎么样:

 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颜色将条件expression式中的1编码为自动复制。

我的猜测是编译器看到expression式的返回types为Object。

Interesting Posts