finally子句與return語句呼叫先後問題及具體原理解析
分析和例子來源於《深入java虛擬機器》一書。
finally語句一定會執行嗎?
答案是不一定。
雖然很多地方都強調一些釋放資源的方法要在finally
語句塊中執行,因為finally
語句塊一定會執行,但是其實也是有條件的。
finally
語句不執行的兩個條件:
在執行
try{}catch
語句之前已經退出程式在
try{}catch
語句中執行了System.exit()
語句導致JVM退出並停止工作
只要是程式到達了try{}catch
語句塊,除非JVM停止工作,否則都會執行finally
語句。那如果try{}catch
語句或者finally
語句中含有return
語句,會不會結束程式呢?如果不,那又是怎樣工作的呢?
finally子句的呼叫過程
首先看表面,JVM在每個try
語句塊和與其相關的catch
子句的結尾處都會“呼叫”finally
子句,finally
子句結束後(這裡結束指的是finally
子句的最後一條語句正常執行完畢,不包括丟擲異常,或者執行return、continue、break
等情況),執行返回操作,程式在第一次呼叫finally
語句的地方繼續執行後面的語句。
然後介紹子例程,子例程就是主程式中的一段程式碼,在主程式中可以呼叫,執行特定的功能同時與主程式相對獨立。書中將finally
子句在方法內部的行為看作是一個“微型子例程”,執行使用者設定的功能同時與外部程式相對獨立。
接著介紹原理,當程式需要執行finally
這裡可能會疑惑,為什麼前面將返回地址壓入棧,而這裡ret
指令儲存的是區域性變數的索引,不應該是從棧中彈出返回地址,執行返回操作嗎?這裡就涉及到了return
等命令。
不對稱的呼叫和返回
不對稱指的是前面壓入棧,後面不通過出棧返回值。實際上,在每個子例程開始處,返回地址都從棧頂端彈出,並且儲存在區域性變數中。稍後的ret
指令會從這個區域性變數中取出返回地址。
這樣的不對稱呼叫是很必要的,因為finally
return、break、continue
等語句,如果在執行finally
子句時遇到了退出語句,可能會導致棧幀被彈出或棧被清空,如果返回地址還被壓入在棧中,會被清除,換句話說也就是finally
無法正常執行完,ret
指令也就不會呼叫,返回地址也就無法拿到,程式就無法從跳轉的地方繼續執行。所以需要儲存在區域性變數中。
下面程式碼包含了一個通過break語句退出的finally
子句,執行的結果是,無論引數傳入什麼,該方法都將返回false
。
public static boolean test(boolean value){
while(value){
try{
return true;
}
finally{
break;
}
}
return false;
}
雖然try
語句中讓方法返回了,但是因為後面緊跟著finally
子句,所以將while
語句終結處的地址壓入棧中,然後開始子例程,在子例程一開始的時候,就將地址所在的棧幀彈出並將返回地址賦值給一個區域性變數,然後執行break
語句退出while
迴圈,最後返回false
。也就是說return
在此例中並沒有起到作用。
實際上,return
還是執行了操作的,return
將返回的值儲存到一個區域性變數中,如果後面finally
子句中執行退出語句,那麼此返回值無效;如果沒有退出,分兩種情況,一種是在finally
子句中改變了需要返回的變數的值,但這種操作並不會影響前面已經儲存了的返回值,程式依然會返回區域性變數中的值,另一種情況是想要在finally
子句中改變返回值,那麼就必須在finally
中加入return
語句,用來返回被finally
子句更新過的返回值。
上面的解釋也可以說明為什麼finally
子句被稱為“微型子例程”,因為它的執行相當獨立,首先程式很大的機率會執行此語句,此外它的執行與主程式關係不大,邏輯上的聯絡不多,更重要的是,它對於主程式中的變數做出的改變大多沒有落到實處,也就是沒有起到作用,像是return
語句,所以子例程很恰當。