天天看點

finally語句塊

finally語句塊是搭配着try語句塊出現的,也就說必須有try語句塊才會有finally語句塊,但是并不是try語句塊都會搭配有finally語句塊出現,我們常見的更多是try...catch...

finally語句塊一般出現的情況如下:

public int operation() {   
       int result = 2016;   
            /*statements*/
       try {        
            /*statements*/
        } catch (Exception e) { 
            /*statements*/
        } finally {        
            /*statements*/
        }
}      

一、finally語句塊的執行時機

  • finally語句并不是每次都執行,隻有線程執行過try語句塊,finally語句塊才會執行。如果線程執行到try語句塊之前就return的話,finally語句是不會執行的。如例程中第一種情況,在try語句塊之前執行return語句将會直接退出函數。
  • 隻要線程進入過try語句塊,不論有沒有抛出異常,finally語句塊都會執行。如例程中的第二種和第三種情況,線程執行進入try語句塊,不管有沒有出現異常,finally語句都正常執行。
  • finally 語句塊是在結果傳回之前執行的。根據第二種和第三種情況,可以發現finally語句執行在main函數列印結果之前。
public class Test {

    public static void main(String[] args) {
        System.out.println("return before try");
        System.out.println("value: " + test(1));
        System.out.println("return after try");
        System.out.println("value: " + test(0));
        System.out.println("return after try catch");
        System.out.println("value: " + test());
    }

    public static int test(int i) {
        if (i == 1)
            return 0;
        try {
            System.out.println("try block");
            return i;
        } finally {
            System.out.println("finally block");
        }
    }

    public static int test(){
        int i = 1;
        try {
            System.out.println("try block");
            i = 1 / 0;
            return 1;
        }catch (Exception e){
            System.out.println("exception block");
            return 2;
        }finally {
            System.out.println("finally block");
        }
    }
}      

執行結果

return before try
value: 0
return after try
try block
finally block
value: 0
return after try catch
try block
exception block
finally block
value: 2      

二、finally語句塊對傳回結果的影響

上面的例子為了不影響讀者了解,在finally語句中沒有做任何可能會影響到傳回結果的操作。但是在finally語句塊中對傳回結果進行操作的話,會不會對傳回結果造成影響就要分情況而論了。在學習接下來這部分知識之前,筆者是堅持認為finally語句對傳回結果進行操作,是肯定會影響傳回結果的值的,畢竟根據上面的了解,finally語句塊執行在結果傳回之前嘛,随着深入學習,筆者發現這裡面還是有需要注意的地方的。先看一段例程:

public class Test {
    public static void main(String[] args) {
        System.out.println("value : " + getValue());
    }

    public static int getValue() {
        int i = 1;
        try {
            return i;
        } finally {
            i++;
        }
    }
}      

執行結果:

value : 1      

按照之前的了解,這個程式執行得有點不按套路出牌啊,執行完try語句之後,程式先不傳回,接着執行finally語句塊,做自增操作,傳回值應該是2啊,怎麼會是1呢?我不知道各位讀者這會什麼心情,反正當時我看到這結果的時候,用一臉懵逼來形容是毫不為過。

結果查閱資料,發現這個地方Java裡面是有特殊處理的,下面這段話是抄的:

“Java 虛拟機會把 finally 語句塊作為 subroutine直接插入到 try 語句塊或者 catch 語句塊的控制轉移語句之前。但是,還有另外一個不可忽視的因素,那就是在執行 subroutine之前,try 或者 catch 語句塊會保留其傳回值到本地變量表中。待 subroutine 執行完畢之後,再恢複保留的傳回值到操作數棧中,然後通過 return 或者 throw 語句将其傳回給該方法的調用者。”

也就是說在try...catch...中執行到return語句的時候,會先把需要傳回的資料緩存下來,再去執行finally語句塊,執行完finally語句塊之後,再将緩存的資料作為結果傳回。這樣看來上面例程的執行結果就說得過去了。是不是好開心,感覺一下子豁然開朗了呢。不要着急,咱們再入一個例程:

public class Test {

    static class Monitor {
        int flag;

        public Monitor(int flag) {
            this.flag = flag;
        }

        public void setFlag(int flag) {
            this.flag = flag;
        }

        public int getFlag() {
            return this.flag;
        }
    }

    public static void main(String[] args) {
        Monitor monitor = getValue();
        System.out.println("flag : " + monitor.getFlag());
    }

    public static Monitor getValue() {
        Monitor monitor = new Monitor(0);
        try {
            return monitor;
        } finally {
            monitor.setFlag(1);
        }
    }
}      

執行結果:

flag : 1      

不是說在執行finally語句之前會把結果緩存的嗎?為什麼傳回的結果會是1,應該是0才對的啊。其實這個地方出現疑問是因為不了解緩存到底存的是什麼。這兩個例程在我們的了解上産生沖突,是因為這兩個例程中,finally語句塊操作的是兩種類型的變量,一個是基本類型,一個是對象類型。這兩種類型的變量的存儲方式是不一樣的。int等基本類型的變量,在變量對應的記憶體中直接存放的就是該變量的值,而對象類型的變量,在其對應的記憶體中存放的是對象所在的位址。執行finally語句之前,Java确實對二者都進行了緩存,緩存的是變量對應的記憶體中存放的資料,于基本類型而言,緩存的是值,于對象類型而言,緩存的是對象所在記憶體區塊的位址。我們在finally語句中進行操作時,不會影響緩存中存放的資料,這樣無論我們對基本類型的值如何操作,傳回值始終是之前的值,但是對象類型是不一樣的,緩存的資料不變隻能保證緩存資料位址指向的一直是同一個對象,finally語句塊中對這個對象進行操作,是完全可以影響到對象的屬性的。

看到這,暫時算是豁然開朗了,也不知道有沒有沒挖出來的坑,如果有的話,還請各位大牛不吝賜教。

繼續閱讀