關(guān)于Java中try finally return語句的執(zhí)行順序淺析
問題分析
finally語句塊一定會執(zhí)行嗎?
可能很多人第一反應是肯定要執(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相對應的try語句塊得到執(zhí)行的情況下,finally語句塊才會執(zhí)行,而上面都是在try語句塊之前返回(return)或者拋出異常,所以try對應的finally語句塊沒有執(zhí)行。那么,即使與finally相對應的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虛擬機的運行,雖然一般情況下我們不會這么干。還有情況是當一個線程在執(zhí)行try語句塊或者catch語句塊時被打斷(interrupted)或者被終止(killed),與其對應的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語句塊應該是在控制轉(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++;語句應該在 try 中的 return i;之前執(zhí)行啊?i 的初始值為 1,那么執(zhí)行 i++;之后為 2,再執(zhí)行 return i;那不就應該是 2 嗎?怎么變成 1 了呢?
說明這個問題需要了解Java虛擬機是如何編譯finally語句塊的。
Java方法是在棧幀中執(zhí)行,棧幀是線程私有棧的單位,執(zhí)行方法的線程會為每一個方法分配一小塊空間來作為該方法執(zhí)行時的內(nèi)存空間,棧幀分為三個區(qū)域:
1、操作數(shù)棧,用來保存正在執(zhí)行的表達式中的操作數(shù)
2、局部變量區(qū),用來保存方法中使用的變量,包括方法參數(shù),方法內(nèi)部聲明的變量,以及方法中使用到的對象的成員變量或類的成員變量(靜態(tài)變量),最后兩種變量會復制到局部變量區(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é)果)復制到局部變量區(qū)作為返回值;
3、執(zhí)行:finally語句塊中的代碼;
4、執(zhí)行:將第2步復制到局部變量區(qū)的返回值又復制回操作數(shù)棧頂;
5、執(zhí)行:return指令,返回操作數(shù)棧頂?shù)闹担?/p>
我們可以看到,在第一步執(zhí)行完畢后,整個方法的返回值就已經(jīng)確定了,由于還要執(zhí)行finally代碼塊,因此程序會將返回值暫存在局部變量區(qū),騰出操作數(shù)棧用來執(zhí)行finally語句塊中代碼,等finally執(zhí)行完畢,再將暫存的返回值又復制回操作數(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)文章
使用JWT創(chuàng)建解析令牌及RSA非對稱加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務器下實現(xiàn)無狀態(tài)分布式身份驗證,需要的朋友可以參考下2023-11-11Java 高并發(fā)十: JDK8對并發(fā)的新支持詳解
本文主要介紹Java 高并發(fā)JDK8的支持,這里整理了詳細的資料及1. LongAdder 2. CompletableFuture 3. StampedLock的介紹,有興趣的小伙伴可以參考下2016-09-09