1. 程式人生 > >finally子句與return語句呼叫先後問題及具體原理解析

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指令,該指令的功能是執行子例程返回的操作,該指令中只有一個運算元,儲存的是返回地址的區域性變數的索引。

這裡可能會疑惑,為什麼前面將返回地址壓入棧,而這裡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語句,所以子例程很恰當。