具有返回types的Java方法编译时没有返回语句
问题1:
为什么下面的代码没有返回语句而编译?
public int a() { while(true); }
注意:如果我在一段时间后添加return,那么我得到一个Unreachable Code Error
。
问题2:
另一方面,为什么下面的代码编译,
public int a() { while(0 == 0); }
即使下面没有。
public int a(int b) { while(b == b); }
问题1:
为什么下面的代码没有返回语句编译?
public int a() { while(true); }
这包含在JLS§8.4.7中 :
如果一个方法被声明为返回types(§8.4.5),那么如果方法的主体可以正常完成(§14.1),则会发生编译时错误。
换句话说,具有返回types的方法只能通过使用提供返回值的返回语句来返回; 该方法不允许“脱身”。 关于方法体中的return语句的精确规则,请参阅§14.17。
一个方法可能有一个返回types,但不包含返回语句。 这里是一个例子:
class DizzyDean { int pitch() { throw new RuntimeException("90 mph?!"); } }
由于编译器知道循环将永远不会终止(当然是true),它知道函数不能“正常返回”(抛弃它的主体),因此没有任何return
。
问题2:
另一方面,为什么下面的代码编译,
public int a() { while(0 == 0); }
即使下面没有。
public int a(int b) { while(b == b); }
在0 == 0
情况下,编译器知道循环将永远不会终止( 0 == 0
将始终为真)。 但是它不知道对于b == b
。
为什么不?
编译器理解常量expression式(第15.28节) 。 引用§15.2 – expressionforms (因为奇怪的是这句话不在§15.28中) :
有些expression式的值可以在编译时确定。 这些是常量expression式 (§15.28)。
在你的b == b
例子中,因为涉及到一个variables,所以它不是一个常量expression式,并且在编译时没有被指定。 我们可以看到,在这种情况下总是会是真的(尽pipe如果b
是double
,就像QBrute 指出的那样 ,我们很容易被Double.NaN
愚弄,而不是==
本身 ),但是JLS只指定常量expression式在编译时确定,它不允许编译器尝试计算非常量expression式。 bayou.io 提出了一个很好的观点 ,为什么不呢:如果你开始在编译时试图确定涉及variables的expression式,那么你停在哪里? b == b
很明显(呃,对于非NaN
值),但是a + b == b + a
呢? 或(a + b) * 2 == a * 2 + b * 2
? 在常量上画线是有意义的。
因此,由于它不“确定”expression式,编译器不知道循环将永远不会终止,所以它认为该方法可以正常返回 – 这是不允许的,因为它需要使用return
。 所以它抱怨没有return
。
认为方法返回types不是返回指定types的值的承诺,而是承诺不返回非指定types的值。 因此,如果你永远不会退还任何东西,那么你就没有违背承诺,所以下列任何一条都是合法的:
-
永远循环:
X foo() { for (;;); }
-
永久recursion:
X foo() { return foo(); }
-
抛出exception:
X foo() { throw new Error(); }
(由于这些方法实际上不会返回值,因此X
的types并不重要。)
查看字节码,如果返回的内容与定义不符,将会收到编译错误。
例:
for(;;)
将显示字节码:
L0 LINENUMBER 6 L0 FRAME SAME GOTO L0
注意缺less任何返回字节码
这并不是一个回报,因此不会返回错误的types。
为了比较,一种方法是:
public String getBar() { return bar; }
将返回以下字节码:
public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn
注意“返回”意味着“返回一个参考”
现在,如果我们做到以下几点:
public String getBar() { return 1; }
将返回以下字节码:
public String getBar(); Code: 0: iconst_1 1: ireturn
现在我们可以看到定义中的types与ireturn的返回types不匹配,这意味着返回int。
所以真正的结果是,如果方法有返回path,该path必须匹配返回types。 但是在字节码中有一些实例根本没有返回path,因此没有违规。