關(guān)于Java中try finally return語句的執(zhí)行順序淺析
問題分析
finally語句塊一定會執(zhí)行嗎?
可能很多人第一反應(yīng)是肯定要執(zhí)行的,但仔細一想,如果一定會執(zhí)行的話 也就不會這么SB的問了。
Demo1
public class Test {
public static void main(String[] args) {
System.out.println("return value of test(): " + test());
}
public static int test() {
int i = 1;
// if (i == 1) {
// return 0;
// }
System.out.println("the previous statement of try block");
i = i / 0;
try {
System.out.println("try block");
return i;
} finally {
System.out.println("finally block");
}
}
}
Demo1的執(zhí)行結(jié)果如下:
the previous statement of try block Exception in thread "main" java.lang.ArithmeticException: / by zero at com.becoda.bkms.bus.basics.web.Test2.test(Test2.java:15) at com.becoda.bkms.bus.basics.web.Test2.main(Test2.java:5)
另外,如果去掉上例中的注釋,執(zhí)行結(jié)果則是:
return value of test(): 0
以上兩種情況,finally語句塊都沒有執(zhí)行,說明什么問題?只有與finally相對應(yīng)的try語句塊得到執(zhí)行的情況下,finally語句塊才會執(zhí)行,而上面都是在try語句塊之前返回(return)或者拋出異常,所以try對應(yīng)的finally語句塊沒有執(zhí)行。那么,即使與finally相對應(yīng)的try語句塊得到執(zhí)行的情況下,finally語句塊一定會執(zhí)行嗎?但下面例子
Demo2
public class Test {
public static void main(String[] args) {
System.out.println("return value of test(): " + test());
}
public static int test() {
int i = 1;
try {
System.out.println("try block");
System.exit(0);
return i;
} finally {
System.out.println("finally block");
}
}
}
Demo2的執(zhí)行結(jié)果如下:
try block
finally語句塊還是沒有執(zhí)行,為什么呢?因為我們在try語句塊中執(zhí)行了System.exit(0)語句,終止了Java虛擬機的運行,雖然一般情況下我們不會這么干。還有情況是當(dāng)一個線程在執(zhí)行try語句塊或者catch語句塊時被打斷(interrupted)或者被終止(killed),與其對應(yīng)的finally語句塊可能不會執(zhí)行。還有更極端的情況,就是在線程運行 try 語句塊或者 catch 語句塊時,突然死機或者斷電,finally 語句塊肯定不會執(zhí)行了。
finally 語句示例說明
下面看一個簡單的例子
Demo3
public class Test {
public static void main(String[] args) {
try {
System.out.println("try block");
return;
} finally {
System.out.println("finally block");
}
}
}
Demo3的執(zhí)行結(jié)果為:
try block finally block
Demo3說明 finally 語句塊在 try 語句塊中的 return 語句之前執(zhí)行。我們再來看另一個例子。
Demo4
public class Test {
public static void main(String[] args) {
System.out.println("reture value of test() : " + test());
}
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");
}
}
}
Demo4的執(zhí)行結(jié)果為:
try block exception block finally block reture value of test() : 2
Demo4說明了 finally 語句塊在 catch 語句塊中的 return 語句之前執(zhí)行。
從上面的Demo3和Demo4,我們可以看出,其實finally語句塊時在try或者catch中的return語句之前執(zhí)行的,更加一般的說法是,finally語句塊應(yīng)該是在控制轉(zhuǎn)移語句之前執(zhí)行,控制轉(zhuǎn)移語句除了return外,還有break和continue。
再來看下面兩個例子
Demo5
public class Test {
public static void main(String[] args) {
System.out.println("return value of getValue(): " + getValue());
}
public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}
Demo5的執(zhí)行結(jié)果為:
return value of getValue(): 1
Demo6
public class Test {
public static void main(String[] args) {
System.out.println("return value of getValue(): " + getValue());
}
public static int getValue() {
int i = 1;
try {
return i;
} finally {
i++;
}
}
}
Demo6的執(zhí)行結(jié)果為:
return value of getValue(): 1
利用我們上面分析得出的結(jié)論:finally 語句塊是在 try 或者 catch 中的 return 語句之前執(zhí)行的。 由此,可以輕松的理解Demo5 的執(zhí)行結(jié)果是 1。因為 finally 中的 return 1;語句要在 try 中的 return 0;語句之前執(zhí)行,那么 finally 中的 return 1;語句執(zhí)行后,把程序的控制權(quán)轉(zhuǎn)交給了它的調(diào)用者 main()函數(shù),并且返回值為 1。那為什么Demo6 的返回值不是 2,而是 1 呢?按照Demo5 的分析邏輯,finally 中的 i++;語句應(yīng)該在 try 中的 return i;之前執(zhí)行???i 的初始值為 1,那么執(zhí)行 i++;之后為 2,再執(zhí)行 return i;那不就應(yīng)該是 2 嗎?怎么變成 1 了呢?
說明這個問題需要了解Java虛擬機是如何編譯finally語句塊的。
Java方法是在棧幀中執(zhí)行,棧幀是線程私有棧的單位,執(zhí)行方法的線程會為每一個方法分配一小塊空間來作為該方法執(zhí)行時的內(nèi)存空間,棧幀分為三個區(qū)域:
1、操作數(shù)棧,用來保存正在執(zhí)行的表達式中的操作數(shù)
2、局部變量區(qū),用來保存方法中使用的變量,包括方法參數(shù),方法內(nèi)部聲明的變量,以及方法中使用到的對象的成員變量或類的成員變量(靜態(tài)變量),最后兩種變量會復(fù)制到局部變量區(qū),因此在多線程環(huán)境下,這種變量需要根據(jù)需要聲明為volatile類型
3、字節(jié)碼指令區(qū)
例如下面這段代碼
try{
return expression;
}finally{
do some work;
}
首先我們知道,finally語句是一定會執(zhí)行,但他們的執(zhí)行順序是怎么樣的呢?他們的執(zhí)行順序如下:
1、執(zhí)行:expression,計算該表達式,結(jié)果保存在操作數(shù)棧頂;
2、執(zhí)行:操作數(shù)棧頂值(expression的結(jié)果)復(fù)制到局部變量區(qū)作為返回值;
3、執(zhí)行:finally語句塊中的代碼;
4、執(zhí)行:將第2步復(fù)制到局部變量區(qū)的返回值又復(fù)制回操作數(shù)棧頂;
5、執(zhí)行:return指令,返回操作數(shù)棧頂?shù)闹担?/p>
我們可以看到,在第一步執(zhí)行完畢后,整個方法的返回值就已經(jīng)確定了,由于還要執(zhí)行finally代碼塊,因此程序會將返回值暫存在局部變量區(qū),騰出操作數(shù)棧用來執(zhí)行finally語句塊中代碼,等finally執(zhí)行完畢,再將暫存的返回值又復(fù)制回操作數(shù)棧頂。所以無論finally語句塊中執(zhí)行了什么操作,都無法影響返回值,所以試圖在finally語句塊中修改返回值是徒勞的。因此,finally語句塊設(shè)計出來的目的只是為了讓方法執(zhí)行一些重要的收尾工作,而不是用來計算返回值的。
這樣就能解釋Demo6的問題了
讓我們再來看以下 3 個例子。
Demo7
public class Test {
public static void main(String[] args) {
System.out.println("return value of getValue(): " + getValue());
}
@SuppressWarnings("finally")
public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
return i;
}
}
}
Demo7的執(zhí)行結(jié)果為:
return value of getValue(): 5
Demo8
public class Test {
public static void main(String[] args) {
System.out.println("return value of getValue(): " + getValue());
}
public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
}
return i;
}
}
Demo8的執(zhí)行結(jié)果為:
return value of getValue(): 5
Demo9
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
public static String test() {
try {
System.out.println("try block");
return test1();
} finally {
System.out.println("finally block");
}
}
public static String test1() {
System.out.println("return statement");
return "after return";
}
}
Demo9的執(zhí)行結(jié)果為:
try block return statement finally block after return
總結(jié):
1、finally 語句塊不一定會被執(zhí)行
2、finally 語句塊在 try 語句塊中的 return 語句之前執(zhí)行
3、finally 語句塊在 catch 語句塊中的 return 語句之前執(zhí)行
4、finally 語句塊中的 return 語句會覆蓋 try 塊中的 return 返回
5、試圖在 finally 語句塊中修改返回值不一定會被改變
相關(guān)文章
eclipse啟動出現(xiàn)“failed to load the jni shared library”問題解決
這篇文章主要介紹了eclipse啟動出現(xiàn)“failed to load the jni shared library”問題解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
Java使用TCP實現(xiàn)數(shù)據(jù)傳輸實例詳解
這篇文章主要介紹了Java使用TCP實現(xiàn)數(shù)據(jù)傳輸實例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
MyBatis動態(tài)SQL中的trim標(biāo)簽的使用方法
這篇文章主要介紹了MyBatis動態(tài)SQL中的trim標(biāo)簽的使用方法,需要的朋友可以參考下2017-05-05
出現(xiàn)java.lang.NoSuchMethodException異常的解決(靠譜)
這篇文章主要介紹了出現(xiàn)java.lang.NoSuchMethodException異常的解決方案(靠譜),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Intellij IDEA 2018配置Java運行環(huán)境的方法步驟
這篇文章主要介紹了Intellij IDEA 2018配置Java運行環(huán)境的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

