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語句塊中對這個對象進行操作,是完全可以影響到對象的屬性的。
看到這,暫時算是豁然開朗了,也不知道有沒有沒挖出來的坑,如果有的話,還請各位大牛不吝賜教。