[英]Missing return statement in a non-void method compiles
我遇到一种情况,其中一个非void方法缺少一个return语句,代码仍然编译。 我知道while循环之后的语句是无法访问的 (死代码),永远不会被执行。 但为什么编译器甚至不警告返回什么? 或者为什么一种语言允许我们使用无效循环且不返回任何内容的非void方法?
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
如果我在while循环中添加break语句(甚至是条件语句),编译器会抱怨臭名昭着的错误: Method does not return a value
在Eclipse中Method does not return a value
,并且Not all code paths return a value
在Visual Studio中Not all code paths return a value
。
public int doNotReturnAnything() {
while(true) {
if(mustReturn) break;
//do something
}
//no return statement
}
Java和C#都是如此。
为什么一种语言允许我们使用具有无限循环且不返回任何内容的非void方法?
非void方法的规则是返回的每个代码路径都必须返回一个值 ,并且该规则在您的程序中得到满足:返回的零代码路径中的零返回值。 规则不是“每个非void方法都必须有一个返回的代码路径”。
这使您可以编写存根方法,如:
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
这是一种非空洞的方法。 为了满足界面,它必须是非空方法。 但是让这个实现变得非法似乎很愚蠢,因为它不会返回任何东西。
你的方法有一个无法到达的终点,因为goto
(记住,一段while(true)
只是一种更愉快的写goto
)而不是throw
(这是另一种形式的goto
)是不相关的。
为什么编译器甚至不警告返回什么?
因为编译器没有很好的证据证明代码是错误的。 有人在写的while(true)
看起来那个人知道他们在做什么。
我在哪里可以阅读有关C#中可达性分析的更多信息?
在这里查看我关于这个主题的文章:
您也可以考虑阅读C#规范。
Java编译器非常智能,可以找到无法访问的代码( while
循环后的代码)
而且由于其无法访问 , 没有点中加入了return
声明有(后while
结束)
同样适用于条件if
public int get() {
if(someBoolean) {
return 10;
}
else {
return 5;
}
// there is no need of say, return 11 here;
}
由于布尔条件someBoolean
只能求值为true
或false
,因此不需要在if-else
之后显式提供return
,因为该代码无法访问 ,Java也不会抱怨它。
编译器知道while
循环永远不会停止执行,因此该方法永远不会完成,因此不需要return
语句。
鉴于你的循环正在一个常量上执行 - 编译器知道它是一个无限循环 - 意味着该方法永远不会返回。
如果使用变量 - 编译器将强制执行规则:
这不会编译:
// Define other methods and classes here
public int doNotReturnAnything() {
var x = true;
while(x == true) {
//do something
}
//no return statement - won't compile
}
Java规范定义了一个名为Unreachable statements
的概念。 您不允许在代码中包含无法访问的语句(这是编译时错误)。 你甚至不允许在while之后有一个return语句(true); Java中的声明。 while(true);
语句使得以下语句无法通过定义访问,因此您不需要return
语句。
请注意,虽然在一般情况下Halting问题是不可判定的,但是Unreachable Statement的定义比仅停止更严格。 它正在决定一个程序肯定不会停止的非常具体的情况。 理论上,编译器无法检测所有无限循环和无法访问的语句,但它必须检测规范中定义的特定情况(例如, while(true)
情况)
编译器非常聪明,可以发现你的while
循环是无限的。
所以编译器无法为你考虑。 它不能猜测,为什么你写的代码。 同样代表方法的返回值。 如果你不对方法的返回值做任何事情,Java就不会抱怨。
那么,回答你的问题:
编译器分析您的代码,并在发现没有执行路径导致函数结束时,它就完成了OK。
无限循环可能有合理的原因。 例如,许多应用程序使用无限主循环。 另一个例子是可以无限期等待请求的Web服务器。
在类型理论中,有一种称为底部类型的东西,它是所有其他类型(!)的子类,用于指示其他事物的非终止。 (例外可以算作非终止类型 - 您不会通过正常路径终止。)
所以从理论的角度来看,这些非终止的语句可以被认为是返回Bottom类型的东西,它是int的子类型,所以你从类型的角度来看(从某种程度上)得到你的返回值。 并且完全可以,没有任何意义,一个类型可以是包括int在内的所有其他类型的子类,因为你实际上从未返回过一个类型。
在任何情况下,通过显式类型理论,编译器(编译器编写者)都认识到在非终止语句之后要求返回值是愚蠢的:没有可能需要该值的情况。 (让你的编译器在知道某些东西不会终止但是看起来你想让它返回一些东西时警告你可能会很高兴。但是对于样式检查器而言,这样做更好,因为你可能需要类型签名,出于某种其他原因(例如子类化),但你真的想要非终止。)
在没有返回适当值的情况下,函数无法达到目的。 因此,编译器没有什么可抱怨的。
Visual Studio有智能引擎来检测你是否输入了一个返回类型,然后它应该在函数/方法中有一个return语句。
与在PHP中一样,如果您还没有返回任何内容,则返回类型为true。 如果没有返回,则编译器获得1。
截至此
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
编译器知道虽然语句本身具有infinte性质,所以不要考虑它。 如果你在while的表达式中写一个条件,PHP编译器将自动获得。
但是在VS的情况下它不会在堆栈中返回错误。
你的while循环将永远运行,因此不会出现在外面; 它将继续执行。 因此,while {}的外部部分是无法访问的,并且没有写入返回的点。 编译器足够智能,可以确定哪些部分可以访问,哪些部分不可访问。
例:
public int xyz(){
boolean x=true;
while(x==true){
// do something
}
// no return statement
}
上面的代码将无法编译,因为可能存在变量x的值在while循环体内被修改的情况。 所以这使得while循环的外部部分可以访问! 因此编译器将抛出错误'找不到返回语句'。
编译器不够智能(或者说是懒惰;))来确定x的值是否会被修改。 希望这能清除一切。
“为什么编译器甚至不会警告返回某些内容?或者为什么语言允许我们使用无效循环并且不返回任何内容的非void方法?”。
此代码在所有其他语言中也有效(可能除了Haskell!)。 因为第一个假设是我们“有意”地编写了一些代码。
并且有些情况下这个代码可以完全有效,就像你要将它用作线程一样; 或者如果它返回一个Task<int>
,你可以根据返回的int值做一些错误检查 - 不应该返回。
我可能错了,但有些调试器允许修改变量。 这里虽然x没有被代码修改而且它将被JIT优化,但是可能会将x修改为false并且方法应该返回一些内容(如果C#调试器允许这样的话)。
Java的具体情况(可能与C#案例非常相似)与Java编译器如何确定方法是否能够返回有关。
具体而言,规则是用返回类型的方法必须不能正常完成 ,而必须始终完成突然 (突然在这里通过return语句或异常指示)每JLS 8.4.7 。
如果声明方法具有返回类型,则如果方法的主体可以正常完成,则会发生编译时错误。 换句话说,具有返回类型的方法必须仅使用提供值返回的return语句返回; 它不允许“脱落其身体的末端” 。
编译器根据JLS 14.21无法到达语句中定义的规则查看是否可以进行正常终止 ,因为它还定义了正常完成的规则。
值得注意的是,无法访问语句的规则仅针对具有已定义的true
常量表达式的循环进行了特殊处理:
如果至少满足下列条件之一,则while语句可以正常完成:
while语句是可访问的,条件表达式不是值为true的常量表达式(第15.28节)。
有一个可到达的break语句退出while语句。
因此,如果while
语句可以正常完成 ,那么它下面的return语句是必要的,因为代码被认为是可达的,并且任何没有可达到的break语句或常量true
表达式的while
循环都被认为能够正常完成。
这些规则意味着具有常量true表达式且没有break
的while
语句永远不会被认为是正常完成的 ,因此它下面的任何代码都不会被认为是可访问的 。 该方法的结尾位于循环之下,并且由于循环下方的所有内容都无法访问,因此方法的结束也是如此,因此该方法无法正常完成 (这是编译器所寻求的)。
另一方面, if
语句没有关于循环提供的常量表达式的特殊豁免。
相比:
// I have a compiler error!
public boolean testReturn()
{
final boolean condition = true;
if (condition) return true;
}
附:
// I compile just fine!
public boolean testReturn()
{
final boolean condition = true;
while (condition)
{
return true;
}
}
区分的原因非常有趣,并且是由于希望允许不会导致编译器错误的条件编译标志(来自JLS):
有人可能希望以下列方式处理if语句:
如果至少满足下列条件之一,则if-then语句可以正常完成:
if-then语句是可访问的,条件表达式不是值为true的常量表达式。
then语句可以正常完成。
如果if-then语句可以访问且条件表达式不是值为false的常量表达式,那么then语句是可到达的。
如果then语句可以正常完成或者else语句可以正常完成,则if-then-else语句可以正常完成。
如果if-then-else语句可以访问且条件表达式不是值为false的常量表达式,那么then语句是可到达的。
如果if-then-else语句可达且条件表达式不是值为true的常量表达式,则else语句是可到达的。
这种方法与其他控制结构的处理一致。 但是,为了便于将“if”语句用于“条件编译”目的,实际规则有所不同。
例如,以下语句导致编译时错误:
while (false) { x=3; }
while (false) { x=3; }
因为语句x=3;
无法到达; 但表面上类似的情况:
if (false) { x=3; }
if (false) { x=3; }
不会导致编译时错误。 优化编译器可以实现语句x=3;
将永远不会执行,并可能选择从生成的类文件中省略该语句的代码,但语句x=3;
在此处指定的技术意义上,不被视为“无法到达”。这种不同处理的基本原理是允许程序员定义“标志变量”,例如:
static final boolean DEBUG = false;
然后编写如下代码:
if (DEBUG) { x=3; }
if (DEBUG) { x=3; }
的想法是,它应该是可以调试的从错误的值更改为真或真亦假,然后用无其他修改程序文本正确编译代码。
为什么条件break语句会导致编译器错误?
如循环可达性规则中所引用的,如果while循环包含可到达的break语句,则它也可以正常完成。 由于if
语句的then子句的可达性规则根本不考虑if
的条件,所以这样的条件if
语句的then子句总是被认为是可达的。
如果break
是可达的,那么循环后的代码再次也被认为是可达的。 由于在循环之后没有可达到的代码导致突然终止,因此该方法被认为能够正常完成,因此编译器将其标记为错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.