問題背景
finally語句一定執行嗎
網上有很多人探討Java中異常捕獲機制 try...catch...finally 塊中的finally語句是不是一定會被執行?很多人都說不是,當然他們的回答是正確的,至少有兩種情況下finally語句是不會被執行的:
(1)try語句沒有被執行到就不會執行finally。如在try語句之前程序結束執行,那么finally語句就不會執行,這也說明了finally語句被執行的必要而非充分條件是相應的try語句一定被執行到。
(2)在try塊中執行了System.exit(0)。System.exit(0)的作用是終止Java虛擬機,連JVM都停止了,所有都結束了,當然finally語句也不會被執行到。
finally語句與return的執行順序
還有很多人探討finally語句的執行與return的關系,讓人頗為迷惑,不知道finally語句是在try的return之前執行還是之后執行?我覺得應該是:finally語句是在try的return語句執行之后,return返回之前執行。這樣的說法有點矛盾,也許是我表述不太清楚,下面我給出自己試驗的一些結果和示例進行佐證,有什么問題歡迎大家提出來。
記return后的語句為y=f(),則finally語句在f()執行之后、y返回之前執行。
問題分析
1. finally語句在f()執行之后、y返回之前執行
public class FinallyTest1 {
public static void main(String[] args) {
System.out.println(test1());
}
public static String test1() {
try {
System.out.println("try block");
// ①計算 f() 結果,記作 y,② 執行 finally,③ 返回 y
return test2();
} finally {
System.out.println("finally block");
}
}
public static String test2() {
System.out.println("return statement");
return "after return";
}
運行結果為:
try block
return statement
finally block
after return
說明try中的f() = test2() 先執行了,得到結果y,並且待finally塊執行結束后再返回y。這里大家可能會想:如果finally里也有return語句,那么是不是就直接返回了,try中的return就不能返回了?看下面。
2. finally塊中的return語句覆蓋try塊中的y
public class FinallyTest2 {
public static void main(String[] args) {
System.out.println(test2());
}
public static int test2() {
int b = 20;
try {
System.out.println("try block");
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
return 200;//可以被“具體值”覆蓋
}
// return b;
}
運行結果是:
try block
finally block
b>25, b = 100
200
這說明finally里的return直接返回了,就不管try中是否還有返回語句,這也是不建議在finally中使用return的原因。這里還有個小細節需要注意,finally里加上return過后,try...catch...finally
外面的return b就變成不可到達語句了,也就是永遠不能被執行到,所以需要注釋掉;否則,編譯失敗。
那么直接從finally中返回了,這也是不建議在finally中return的原因。
這里大家可能又想:如果finally里沒有return語句,但修改了b的值,那么try中return返回的是修改后的值還是原值?看下面。
3. finally語句塊不改變棧中的值,可改變堆中的值
本節討論如下場景:finally中沒有return語句,但是改變了要返回的值。返回值y的類型如果是基本類型,則在finally塊里被修改也不影響y,如果是引用類型則可以。如果y是文本字符串,也不受影響。
返回基本類型變量時是值,返回引用類型時是指向某個對象的地址;而且基本類型是被分配在棧中的,對象是被分配在堆中的,只要有引用指向這個對象,系統就不會回收此對象,所以可以在后面的finally塊中改變引用指向的對象的內容,卻無法改變try語句中return要返回的值,因為這個值已經與變量y無關了。
測試用例1:
public class FinallyTest3 {
public static void main(String[] args) {
System.out.println(test3());
}
public static int test3() {
int b = 20;
try {
System.out.println("try block");
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b = 150; //基本類型的返回值y,不可被修改
// return 3200; //可以被“具體值”覆蓋
}
return 2000;
}
}
運行結果是:
try block
finally block
b>25, b = 100
100
執行結果說明沒有修改y。這也驗證了finally語句塊不會影響到棧中的值,即在執行finally之前,棧中的臨時值已經確定為100了,執行finally語句將b的值變為150,對結果沒有任何影響,執行完finally后的輸出結果仍為100。
測試用例2:
import java.util.*;
public class FinallyTest6{
public static void main(String[] args) {
System.out.println(getMap().get("KEY").toString());
}
public static Map<String, String> getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
} catch (Exception e) {
map.put("KEY", "CATCH");
} finally {
map.put("KEY", "FINALLY");// 修改y成功
map = null;
System.out.println("map是:" + map);
}
System.out.println("---- 走不到此處 --------");// ①
return map;
}
運行結果是:
map是:null
FINALLY
為什么案例 test3()中finally里的b = 150並沒有起到作用而案例 getMap()中finally的map.put("KEY", "FINALLY")起了作用,但是map = null卻沒起作用呢?這就是Java到底是傳值還是傳址的問題了,簡單來說就是:Java中只有傳值沒有傳址,這也是為什么map = null這句不起作用。 ①處的代碼未執行說明finally外面的return 這句未執行。
測試用例3:
public static String testStr() {
String b = "init";
try {
System.out.println("try block");
return b;
} catch (Exception e) {
System.out.println("catch block");
} finally {
b = b + "--";
System.out.println("finally block, b = " + b);
}
return b;
}
執行結果:
try block
finally block, b = init--
init
說明如果y是文本字符串,在finally語句塊中改變它,也影響返回值。
這里大家可能又要想:是不是每次返回的一定是try中的return語句呢?那么finally外的return 語句一點作用沒嗎?請看下面。
4. try塊里的return在異常情況下不執行
public class FinallyTest4 {
public static void main(String[] args) {
System.out.println(test4());
}
public static int test4() {
int b = 20;
try {
System.out.println("try block");
b = b / 0;
return b += 80; // ①
} catch (Exception e) {
b += 15;
System.out.println("catch block, b = " + b);
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
System.out.println("---- 走此處 --------" + b);
return 320 + b; // ②
}
運行結果是:
try block
catch block, b = 35
finally block
b>25, b = 35
---- 走此處 --------85
405
這里因 為在return之前發生了除0異常,所以①不會被執行,而是接着執行捕獲異常的catch 語句和最終的finally語句。此時兩者對b的修改都影響了最終的返回值,從②返回的結果可以看出來。大家可能又有疑問:如果catch中有return語句呢?當然只有在異常的情況下才有可能會執行,那么是在finally之前就返回嗎?看下面。
5. catch和try中的return執行順序相同
public class FinallyTest5 {
public static void main(String[] args) {
System.out.println(test5());
}
public static int test5() {
int b = 20;
try {
System.out.println("try block");
b = b /0;
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
return b += 15;
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
//return b;
}
}
運行結果如下:
try block
catch block
finally block
b>25, b = 35
35
執行結果說明發生異常后,catch中的return語句先執行,計算完返回值y后將其保存起來,再去執行finally塊;執行完finally塊就把先去保存的y返回,finally里修改b對返回值y無影響,原因同前面的 test3() 。至此,也就推理出結論:catch中的return語句和try中的執行順序完全一樣。
小結
- finally塊的語句在try或catch中的return語句執行之后返回之前執行。
- finally語句塊不改變棧中的值y,可改變堆中的值。
y的類型如果是基本類型,其臨時存放在棧中,則在finally塊里被修改也不影響y,如果是存放在堆中的引用類型則可以。finally塊里改變文本字符串也不影響y。 - finally里的return語句覆蓋try或catch中的return語句直接返回。
下面看一個甲骨文的面試題:
public class test {
public static void main(String[] args) {
try {
aMethod();
} catch (Exception e) {
System.out.println("exception");// ①
}
System.out.println("finish");// ②
}
public static void aMethod() throws Exception {
try {
throw new Exception();
} finally {
System.out.println("finally"); // ③
}
}
}
面試題分析:首先,進入上述try代碼塊后,不管try是否拋異常,finally塊一定會執行,因此拋異常之前會執行 ③。然后,拋出的異常被調用者main方法捕獲后執行①。最后,執行②。故執行結果如下:
finally
exception
finish