[英]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.