Java方法有返回类型但没有返回语句也可以编译通过

230

问题1:

为什么以下代码没有返回语句也能编译通过?

public int a() {
    while(true);
}

注意:如果我在 while 循环后添加 return,则会出现“不可访问的代码错误(Unreachable Code Error)”。

问题2:

另一方面,为什么以下代码可以编译通过,

public int a() {
    while(0 == 0);
}

尽管以下内容并非如此。

public int a(int b) {
    while(b == b);
}

2
不是https://dev59.com/c2Qn5IYBdhLWcg3wgnPg的重复,感谢第二个问题的后半部分。 - T.J. Crowder
3个回答

275

Question 1:

Why does the following code compile without having a return statement?

public int a() 
{
    while(true);
}

这是由JLS§8.4.7覆盖的:

If a method is declared to have a return type (§8.4.5), then a compile-time error occurs if the body of the method can complete normally (§14.1).

In other words, a method with a return type must return only by using a return statement that provides a value return; the method is not allowed to "drop off the end of its body". See §14.17 for the precise rules about return statements in a method body.

It is possible for a method to have a return type and yet contain no return statements. Here is one example:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

编译器知道循环永远不会终止(当然,true 总是为真),因此它知道函数不能“正常返回”(从其主体中掉出末尾),因此没有 return 也是可以的。

Question 2:

On the other hand, why does the following code compile,

public int a() 
{
    while(0 == 0);
}

even though the following does not.

public int a(int b)
{
    while(b == b);
}
0 == 0 的情况下,编译器知道循环永远不会终止(即0 == 0 将始终为真)。但是对于 b == b,它不知道
为什么呢?
编译器理解常量表达式(§15.28)。引用 §15.2 - 表达式的形式(因为奇怪地这句话不在§15.28中)

一些表达式具有可以在编译时确定的值。这些是 常量表达式(§15.28)。

在您的b == b示例中,由于涉及变量,它不是一个常量表达式,也没有指定在编译时确定。我们可以看到在这种情况下它总是成立的(尽管如果bdouble,就像QBrute指出的那样,我们可能会被Double.NaN欺骗,因为它本身不是==),但JLS只规定常量表达式在编译时确定,它不允许编译器尝试评估非常量表达式。bayou.io提出了一个很好的观点:如果您开始沿着在编译时确定涉及变量的表达式的道路走下去,您会在哪里停止?对于常量来划分界限是有意义的,b == b是显而易见的(对于非NaN值),但是a + b == b + a呢?或者(a + b) * 2 == a * 2 + b * 2
因为它并未“确定”表达式,编译器不知道循环永远不会终止,因此它认为该方法可以正常返回——但实际上它不能这样做,因为必须使用return。所以编译器会抱怨缺少return

34

将方法的返回类型视为不仅仅是返回指定类型值的承诺,而是承诺不会返回非指定类型的值可能更有趣。因此,如果您从未返回任何内容,则不会违反承诺,因此以下任何一种情况都是合法的:

  1. 无限循环:

X foo() {
    for (;;);
}
  • 无限递归:

    X foo() {
        return foo();
    }
    
  • 抛出异常:

  • X foo() {
        throw new Error();
    }
    

    (我觉得递归的这个很有趣:编译器相信这个方法会返回一个类型为X的值(无论那是什么),但事实并非如此,因为没有任何代码知道如何创建或获取一个X。)


    8

    看着字节码,如果返回的内容与定义不符,你会收到编译错误。

    示例:

    for(;;) 会显示字节码:

    L0
        LINENUMBER 6 L0
        FRAME SAME
        GOTO L0
    

    注意缺少任何返回字节码

    这段代码不会返回任何值,因此不会返回错误的类型。

    相比之下,像下面这个方法:

    public String getBar() { 
        return bar; 
    }
    

    将返回以下字节码:
    public java.lang.String getBar();
        Code:
          0:   aload_0
          1:   getfield        #2; //Field bar:Ljava/lang/String;
          4:   areturn
    

    请注意 "areturn" 表示 "返回一个引用"。现在如果我们执行以下操作:
    public String getBar() { 
        return 1; 
    }
    

    将返回以下字节码:

    public String getBar();
      Code:
       0:   iconst_1
       1:   ireturn
    

    现在我们可以看到定义中的类型与ireturn返回类型不匹配,这意味着返回int。
    因此,实际上问题在于,如果该方法有返回路径,则该路径必须与返回类型匹配。但是,在字节码中存在没有生成任何返回路径的情况,因此不会违反规则。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接