缺less非void方法中的return语句编译

我遇到了一个非void方法缺lessreturn语句并且代码仍在编译的情况。 我知道while循环之后的语句是无法访问的 (死代码),永远不会被执行。 但是,为什么编译器甚至不会提醒你返回什么? 或者为什么一种语言允许我们有一个无限循环而不返回任何东西的非空方法?

public int doNotReturnAnything() { while(true) { //do something } //no return statement } 

如果我在while循环中添加一个break语句(即使是条件语句),编译器会抱怨臭名昭着的错误:“方法不返回值”(Eclipse)和“不是所有的代码path都返回值”(Visual Studio)

 public int doNotReturnAnything() { while(true) { if(mustReturn) break; //do something } //no return statement } 

Java和C#都是如此

为什么一种语言允许我们有一个无效的方法有一个无限循环,不返回任何东西?

非void方法的规则是返回的每条代码path都必须返回一个值 ,并且该规则在您的程序中得到满足:零返回的零代码path返回一个值。 规则不是“每个非void方法都必须有返回的代码path”。

这使您能够编写存根方法,如:

 IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } 

这是一个非无效的方法。 为了满足界面,它必须是一个非空的方法。 但是这个实现是非法的,因为它不返回任何东西。

你的方法有一个无法达到的终点,因为goto (记住,一段while(true)只是一个更好的方式来写goto )而不是throw (这是另一种forms的goto )是不相关的。

为什么编译器甚至不警告返回什么?

因为编译器没有很好的证据表明代码是错误的。 有人写的是while(true) ,看起来可能是那个知道自己在做什么的人。

我在哪里可以阅读更多关于在C#中的可达性分析?

看到我关于这个问题的文章,在这里:

ATBG:事实上和法律上的可达性

你也可以考虑阅读C#规范。

Java编译器足够聪明,可以find无法访问的代码( while循环之后的代码)

而且由于它无法访问所以在那里添加一个return语句没有意义(在结束之后)

与条件if

 public int get() { if(someBoolean) { return 10; } else { return 5; } // there is no need of say, return 11 here; } 

由于布尔条件someBoolean只能求值为truefalse ,因此不需要在if-else之后明确地提供return ,因为该代码是不可访问的 ,Java不会对此抱怨。

编译器知道while循环永远不会停止执行,因此该方法永远不会结束,因此不需要return语句。

鉴于你的循环是在一个常量上执行 – 编译器知道这是一个无限循环 – 意味着方法永远不会返回。

如果你使用一个variables – 编译器将执行这个规则:

这不会编译:

 // 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的概念。 您不允许在代码中有无法访问的语句(这是编译时错误)。 (true)之后,你甚至不允许有return语句; 在Java中的声明。 while(true); 语句根据定义使得以下语句无法访问,因此您不需要return语句。

请注意,虽然在一般情况下Halting问题是不可判定的,但是不可达声明的定义比仅停止更严格。 它决定了一个程序绝对不会停止的非常具体的情况。 编译器在理论上不能检测到所有无限循环和无法访问的语句,但它必须检测规范中定义的特定情况(例如, while(true)情况)

编译器很聪明,可以发现while循环是无限的。

所以编译器不能为你想。 它不能猜测你为什么写这个代码。 同样代表方法的返回值。 如果你不用方法的返回值做任何事情,Java不会抱怨。

所以,要回答你的问题:

编译器分析你的代码,发现没有执行path导致function的结束,并以OK结束。

无限循环可能是合法的原因。 例如,很多应用程序使用无限的主循环。 另一个例子是可以无限等待请求的networking服务器。

没有任何情况下函数可以在不返回适当值的情况下达到最终结果。 因此,编译器没有什么可抱怨的。

在types理论中,有一种叫做bottomtypes的东西,它是所有其他types(!)的子类,用来表示非终结等等。 (例外可以算作一种非终止types – 你不能通过正常的path终止。)

所以从理论的angular度来看,这些非终止的语句可以被认为是返回Bottomtypes的东西,它是int的一个子types,所以你可以从types的angular度来获得你的返回值。 而且,没有任何意义的是,一种types可以成为包括int在内的所有其他类的子类,这是完全没问题的,因为你从来没有真正返回过。

在任何情况下,编译器(编译器编写者)通过显式types理论认识到,在非终止语句之后要求返回值是愚蠢的:不存在可能需要该值的情况。 (当你的编译器知道某个东西不会终止的时候让你的编译器发出警告是很好的,但是看起来你希望它返回一些东西,但是最好还是留一下样式检查器,因为也许你需要types签名出于某种其他原因(例如,子类化)的方式,但是你真的想要不终止。)

Visual Studio有智能引擎来检测你是否已经键入了返回types,那么它应该在函数/方法中有一个return语句。

和PHP一样,如果你还没有返回任何东西,你的返回types是真的。 编译器得到1,如果没有返回。

至于这个

 public int doNotReturnAnything() { while(true) { //do something } //no return statement } 

编译器知道,虽然声明本身具有无限的性质,所以不予考虑。 如果在whileexpression式中写入条件,php编译器会自动变为true。

但在VS的情况下,它会返回你在堆栈中的错误。

你的while循环将永远运行,因此不会在外面; 它将继续执行。 因此,while {}的外部部分是不可访问的,并且没有写入返回的要点。 编译器足够聪明,可以确定哪些部分是可到达的,哪些部分不可用。

例:

 public int xyz(){ boolean x=true; while(x==true){ // do something } // no return statement } 

上面的代码不会被编译,因为可能会有这样的情况,variablesx的值在while循环体内被修改。 所以这使得while循环的外部部分可达! 因此编译器会抛出一个错误“找不到返回语句”。

编译器不够聪明(或者懒惰;))来判断x的值是否会被修改。 希望这清除了一切。

“为什么编译器甚至不警告返回什么东西呢?或者为什么一个语言允许我们有一个无效的方法有一个无限循环,不返回任何东西?”。

这个代码在所有其他语言中也是有效的(可能除了Haskell!)。 因为第一个假设是我们“故意”写一些代码。

而且有些情况下,这个代码可能是完全有效的,就像你打算将它用作线程一样; 或者如果它返回一个Task<int> ,你可以做一些基于返回的int值的错误检查 – 不应该返回。

我可能是错的,但一些debugging器允许修改variables。 在这里虽然x没有被代码修改,并且会被JIT优化,但是可能会将x修改为false,并且方法应该返回一些东西(如果C#debugging器允许的话)。

这个Java案例的细节(可能非常类似于C#的情况)与Java编译器如何确定一个方法是否能够返回有关。

具体来说,规则是一个返回types的方法一定不能正常完成 ,而是必须每个JLS 8.4.7都要突然完成(这里突然指出通过返回语句或exception)。

如果一个方法被声明为返回types,那么如果方法的正文可以正常完成,则会发生编译时错误。 换句话说,具有返回types的方法只能通过使用提供返回值的返回语句来返回; 不允许“脱身”

编译器会根据JLS 14.21 Unreachable Statements中定义的规则查看是否可以正常终止 ,因为它还定义了正常完成的规则​​。

值得注意的是,不可达语句的规则为具有定义的true常量expression式的循环提供了一个特殊情况:

如果满足以下至less一个条件,while语句可以正常完成:

  • while语句是可达的,条件expression式不是一个常量expression式(第15.28),其值为true。

  • 有一个可访问的break语句退出while语句。

所以如果while语句可以正常完成 ,那么下面的return语句是必须的,因为代码被认为是可达的,并且没有可达到break语句或者常量trueexpression式的任何while循环被认为能够正常完成。

这些规则意味着你的while语句不会被认为是正常完成的 ,所以它下面的任何代码都不会被认为是可达的 。 方法的结尾在循环之下,并且由于循环下面的所有内容都是不可访问的,所以方法的结尾也是如此,因此方法不可能正常完成 (这是编译器查找的内容)。

另一方面, if语句没有关于循环提供的常量expression式的特殊豁免。

比较:

 // 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语句按以下方式处理:

  • 如果至less满足下列条件之一,则if-then语句可以正常完成:

    • if-then语句是可访问的,并且条件expression式不是一个值为true的常量expression式。

    • then语句可以正常完成。

    如果if-then语句可到达并且条件expression式不是值为false的常量expression式,那么then语句是可访问的。

  • 如果then语句可以正常完成或者else语句可以正常完成,则if-then-else语句可以正常完成。

    • 如果if-then-else语句可到达且条件expression式不是值为假的常量expression式,那么then语句是可达的。

    • 如果if-then-else语句可到达并且条件expression式不是常量expression式,其值为true,则else语句是可达的。

这种方法将与其他控制结构的处理一致。 但是,为了使if语句方便地用于“条件编译”的目的,实际的规则是不同的。

作为一个例子,下面的语句会导致编译时错误:

while (false) { x=3; } while (false) { x=3; }因为语句x=3; 不可达; 但表面上类似的情况:

if (false) { x=3; } if (false) { x=3; }不会导致编译时错误。 一个优化编译器可能会意识到这个语句x=3; 将永远不会执行,并且可能会从生成的类文件中select省略该语句的代码,但是语句x=3; 在这里指定的技术意义上不被认为是“无法达到的”。

这种不同的处理方式的基本原理是允许程序员定义“标志variables”,例如:

static final boolean DEBUG = false; 然后编写代码如:

if (DEBUG) { x=3; } if (DEBUG) { x=3; }这个想法是应该可以将DEBUG的值从false更改为true,或者从true更改为false,然后正确编译代码,而不会对程序文本进行其他更改。

为什么条件break语句会导致编译器错误?

正如在循环可达性规则中所引用的,如果while循环包含可达的break语句,它也可以正常完成。 由于if语句的then条款的可达性规则根本不考虑if的条件,所以这种条件if语句的then语句总是被认为是可达的。

如果break是可达的,那么循环后的代码再次被认为是可达的。 由于没有可达到的代码导致循环之后的突然终止,因此认为该方法能够正常完成,因此编译器将其标记为错误。