189

让我们看一下以下代码段中的简单 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()方法中)来表示三元运算符,这似乎也是错误的,这确实会引发编译时错误!为什么?

4

8 回答 8

120

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

于 2011-11-11T19:36:18.953 回答
40

我认为,Java 编译器解释true ? null : 0为一个Integer表达式,可以隐式转换intNullPointerException.

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

于 2011-11-11T19:32:45.297 回答
32

实际上,这一切都在Java 语言规范中进行了解释。

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

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

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

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

于 2011-11-11T19:50:51.057 回答
25

if语句的情况下,null引用不被视为Integer引用,因为它没有参与强制它被解释为这样的表达式。因此,该错误很容易在编译时被捕获,因为它更明显是一个类型错误。

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

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

    不适用,因为nullis not int

  • 如果第二个和第三个操作数之一是布尔类型,另一个是布尔类型,则条件表达式的类型是布尔类型。

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

  • 如果第二个和第三个操作数之一是空类型,另一个是引用类型,那么条件表达式的类型就是那个引用类型。

    不适用,因为null它是 null 类型,但int不是引用类型。

  • 否则,如果第二个和第三个操作数具有可转换(第 5.1.8 节)为数字类型的类型,则有几种情况: […]

    适用:null被视为可转换为数字类型,并在第 5.1 节中定义。 8 “拆箱转换”扔一个NullPointerException.
于 2011-11-11T20:43:35.437 回答
11

首先要记住的是,Java 三元运算符有一个“类型”,无论第二个或第三个参数的实际/真实类型是什么,编译器都会确定并考虑这一点。根据几个因素,三元运算符类型以不同的方式确定,如Java 语言规范 15.26中所示

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

否则,第二个和第三个操作数分别是S1S2类型。令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的“最不常见的超类” 。TimestampTime

因为null可以自动装箱到任何东西,“最不常见的超类”是Integer类,这将是上面条件表达式(三元运算符)的返回类型。然后返回值将是一个空指针类型Integer,这就是三元运算符将返回的值。

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

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

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

更正:

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

于 2011-11-12T06:57:04.533 回答
4

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

于 2011-11-15T20:09:55.320 回答
2
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
}
于 2013-12-10T20:52:29.633 回答
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。

于 2016-09-08T19:40:54.110 回答