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


186

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


6
此外,int foo = (true ? null : 0)new Integer(null)这两个编译罚款,第二个是自动装箱的明确形式。
2011年

2
@Izkata,这里的问题是让我理解为什么编译器试图自动装箱nullInteger...看起来就像是在向我“猜测”或“使事情起作用” ...
Marsellus Wallace

1
...嗯,我想我在那里有个答案,因为Integer构造函数(我发现所说的文档用于自动装箱)被允许采用String作为参数(可以为null)。但是,他们也说构造函数的行为与方法parseInt()相同,后者在传递null时会抛出NumberFormatException ...
Izkata

3
@Izkata-Integer的String参数c'tor不是自动装箱操作。字符串不能自动装箱为整数。(该函数Integer foo() { return "1"; }不会编译。)
Ted Hopp

5
很酷,学到了有关三元运算符的新知识!
oksayt 2011年

Answers:


118

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


有了您发布的Java语言规范的链接,您认为在上述问题的情况下执行哪一点?最后一个(因为我仍在努力理解capture conversionlub(T1,T2))?另外,是否有可能将装箱应用于空值?这不是像“猜测”吗??
Marsellus Wallace 2011年

´@ Gevorg空指针是指向每个可能对象的有效指针,因此在此不会发生任何不良情况。编译器仅假定null为Integer,然后可以将其自动装箱为int。
Voo

1
@Gevorg-查看nowaq的评论和我对他的帖子的回复。我认为他选择了正确的条款。lub(T1,T2)是T1和T2的类型层次结构中最通用的最具体引用类型。(它们都至少共享对象,因此总是有一个最特定的引用类型。)
Ted Hopp

8
@Gevorg- null在Integer中,它被解释为对Integer的引用(空引用,但这不是问题)。没有从null构造Integer对象,因此没有理由发生NumberFormatException。
特德·霍普

1
@Gevorg-如果查看装箱转换规则并将其应用于null(不是原始数字类型),则适用的子句为“如果p是任何其他类型的值,则装箱转换等效于身份转换”。因此,将装箱转换nullIntegeryields null,而无需调用任何Integer构造函数。
特德·霍普

40

我认为,Java编译器将其解释true ? null : 0为一个Integer表达式,可以将其隐式转换为int,可能会给出NullPointerException

对于第二种情况,该表达式null具有特殊含义 null类型, 请参见,因此代码return null使类型不匹配。


2
我认为这与自动装箱有关?大概第一次返回不会在Java 5之前编译,对吗?
Michael McGowan

如果将Eclipse的遵从性级别设置为5之前,则@Michael似乎是这种情况。
乔纳森·浮士德

@Michael:这肯定看起来像自动装箱(我对Java还是很陌生,无法做出更明确的声明-抱歉)。
弗拉德(Vlad)

1
@Vlad如何将编译器结束了解释true ? null : 0Integer?通过自动装箱0
Marsellus Wallace

1
@Gevorg:看这里否则,第二和第三操作数分别是S1和S2类型。令T1为对S1进行装箱转换所产生的类型,而T2为对S2进行装箱转换所产生的类型。和以下文字。
Vlad11年

32

实际上,所有这些都在Java Language Specification中进行了说明。

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

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

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

尝试使用类似方法进行验证(true ? null : null),您将得到编译器错误。


3
但是规则的该条款不适用:第二个和第三个操作数没有相同的类型。
特德·霍普

1
那么答案似乎在以下语句中:>否则,第二和第三操作数分别为S1和S2类型。令T1为对S1进行装箱转换所产生的类型,而T2为对S2进行装箱转换所产生的类型。条件表达式的类型是将捕获转换(§5.1.10)应用于lub(T1,T2)(§15.12.2.7)的结果。
nowaq 2011年

我认为这是适用的条款。然后,它尝试应用自动拆箱,以便int从函数中返回一个值,这将导致NPE。
特德·霍普

@nowaq我也这么认为。但是,如果您尝试使用“让T1成为将装箱转换应用于S1所产生的类型...” 来明确装箱nullIntegernew Integer(null);您将得到a,NumberFormatException而事实并非如此……
Marsellus Wallace 2011年

@Gevorg我想,因为进行拳击时发生了异常,所以我们在这里没有任何结果。编译器只必须生成遵循其定义的代码-我们只是在完成之前获取异常。
Voo

25

if陈述的情况下,null引用不被视为Integer引用,因为它没有参与强制将其解释为这样的表达式。因此,错误很容易在编译时捕获,因为它显然是一种类型错误。

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

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

    不适用,因为null不是int

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

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

  • 如果第二个操作数和第三个操作数之一为空类型,而另一个的类型为引用类型,则条件表达式的类型为该引用类型。

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

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

    适用:null被视为可转换为数字类型,并在§5.1中定义。 8“取消装箱转换”以引发NullPointerException

如果自动0将if装箱,Integer则编译器将执行Java语言规范中所述的“三元运算符”的最后一种情况。如果是真的,那么我很难相信它将跳转到相同规则的情况3,该情况具有空值和引用类型,从而使三元运算符的返回值成为引用类型(Integer)。 。
Marsellus华莱士

@Gevorg-为什么很难相信三元运算符会返回Integer?这就是正在发生的事情。NPE是通过尝试对表达式值进行拆箱以int从函数返回来生成的。将函数更改为返回an Integer,它将null毫无问题地返回。
特德·霍普

2
@TedHopp:Gevorg正在回应我的答案的较早版本,这是不正确的。您应该忽略差异。
乔恩·普迪

@JonPurdy“据说一种类型可以转换为数字类型,如果它是数字类型,或者它是可以通过取消装箱转换而转换为数字类型的引用类型”,但我认为不null属于此类。此外,然后我们将进入“否则,将应用二进制数值提升(§5.6.2)...注意,二进制数值提升将执行拆箱转换(§5.1.8)...”步骤以确定返回类型。但是取消装箱转换将生成NPE,这仅在运行时发生,而不是在尝试确定三元运算符类型时发生。我仍然困惑..
Marsellus华莱士

@Gevorg:取消装箱发生在运行时。将null其视为具有type int,但实际上等同于throw new NullPointerException(),仅此而已。
乔恩·珀迪

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因为它是Timestamp/ Time对的“最小公共超类” 。

由于null可以自动装箱到任何东西,因此“最小公共超类”是Integer此类,它将是上面条件表达式(三元运算符)的返回类型。返回值将是类型为null的指针,Integer即三元运算符将返回的值。

在运行时,当Java虚拟机取消装箱时,IntegerNullPointerException引发a。发生这种情况是因为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...


1
null在OP的原代码不被装箱。它的工作方式是:编译器假定the null是对Integer的引用。使用三元表达式类型的规则,它确定整个表达式是一个整数表达式。然后,它会生成代码以自动装箱1(如果条件评估为false)。在执行期间,条件求值为,true因此表达式求值为null。尝试int从函数返回时,将null取消装箱。然后抛出NPE。(编译器可能会优化其中的大部分内容。)
Ted Hopp

4

实际上,由于编译器知道,在第一种情况下可以对表达式进行求值,因为编译器知道必须将其评估为Integer,但是在第二种情况下,null无法确定返回值()的类型,因此无法对其进行编译。如果将其强制转换为Integer,则代码将编译。


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
}

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

    }

}

输出是true,true。

Eclipse将条件表达式中的1标记为自动装箱。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.