具有返回类型的 Java 方法编译而不使用返回语句

2022-08-31 05:51:01

问题 1:

为什么下面的代码在没有 return 语句的情况下进行编译?

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

注意:如果我在一段时间后添加返回,那么我得到一个.Unreachable Code Error

问题 2:

另一方面,为什么下面的代码会编译,

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

即使以下情况并非如此。

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

答案 1

问题 1:

为什么下面的代码在没有 return 语句的情况下进行编译?

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

JLS§8.4.7 涵盖了这一点

如果将方法声明为具有返回类型 (§8.4.5),则当该方法的主体可以正常完成时 (§14.1)时,会发生编译时错误。

换句话说,具有返回类型的方法必须仅使用提供值返回的 return 语句进行返回。该方法不允许“从其身体的末端掉落”。有关方法主体中 return 语句的确切规则,请参见 §14.17。

方法可能具有返回类型,但不包含任何返回语句。下面是一个示例:

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

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

问题 2:

另一方面,为什么下面的代码会编译,

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

即使以下情况并非如此。

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

在这种情况下,编译器知道循环永远不会终止(这始终为真)。但是它不知道.0 == 00 == 0b == b

为什么不呢?

编译器理解常量表达式 (§15.28)。引用 §15.2 - 表达式的形式(因为奇怪的是,这句话不在 §15.28 中)

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

在您的示例中,由于涉及一个变量,因此它不是常量表达式,并且未指定在编译时确定。我们可以看到,在这种情况下,它总是正确的(尽管如果是一个,正如QBrute指出的那样,我们很容易被愚弄,这不是==本身),但JLS只指定常量表达式是在编译时确定的,它不允许编译器尝试计算非常量表达式。bayou.io 提出了一个很好的观点:如果你开始在编译时尝试确定涉及变量的表达式,你会在哪里停下来? 是显而易见的(呃,对于非值),但是呢?或?在常量处绘制线条是有意义的。b == bbdoubleDouble.NaNb == bNaNa + b == b + a(a + b) * 2 == a * 2 + b * 2

因此,由于它不“确定”表达式,编译器不知道循环永远不会终止,因此它认为该方法可以正常返回 - 这是不允许的,因为它需要使用。因此,它抱怨缺少.returnreturn


答案 2

将方法返回类型视为不返回指定类型值的承诺,而是不返回不属于指定类型的值的承诺,可能会很有趣。因此,如果您从不退货,则没有违反承诺,因此以下任何一项都是合法的:

  1. 永远循环:

    X foo() {
        for (;;);
    }
    
  2. 永远递归:

    X foo() {
        return foo();
    }
    
  3. 抛出一个异常:

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

(我发现递归是一个有趣的思考:编译器认为该方法将返回一个类型值(无论它是什么),但事实并非如此,因为没有代码知道如何创建或获取.XX