finally與return的執行順序
都知道,finally的執行特點
1、不管有木有出現異常,finally塊中程式碼都會執行;
2、當try和catch中有return時,finally仍然會執行。
那麼問題來了,執行順序是怎麼樣的呢?
一個簡單的測試類及反編譯後的位元組碼:
public class Test { publicstatic void main(String[] args) { System.out.println(test()); } publicstatic int test() { try{ System.out.println("Codesin try block."); return0; }catch (Exception e) { System.out.println("Codesin catch block."); return100; }finally { System.err.println("Codesin finally block."); } } } /* public static int test(); Code: 0: getstatic #2 // Fieldjava/lang/System.out:Ljava/io/PrintStream; 3: ldc #5 // String Codes in try block. 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: iconst_0 9: istore_0 10: getstatic #7 // Field java/lang/System.err:Ljava/io/PrintStream; 13: ldc #8 // String Codes in finallyblock. 15: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 18: iload_0 19: ireturn 20: astore_0 21: getstatic #2 // Fieldjava/lang/System.out:Ljava/io/PrintStream; 24: ldc #10 // String Codes in catchblock. 26: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 29: bipush 100 31: istore_1 32: getstatic #7 // Fieldjava/lang/System.err:Ljava/io/PrintStream; 35: ldc #8 // String Codes in finallyblock. 37: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 40: iload_1 41: ireturn 42: astore_2 43: getstatic #7 // Fieldjava/lang/System.err:Ljava/io/PrintStream; 46: ldc #8 // String Codes in finallyblock. 48: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 51: aload_2 52: athrow */
可以將我們編寫的程式碼和編譯後的位元組碼做一下對比:
發現虛擬機器為我們做了好多事,它將finally語句塊插到try,catch與return語句之間了。這也就是為什麼無論異常與否,返回與否,finally都會執行的原因。既然,finally都會執行,那麼在finally中return什麼就是什麼。既然finally前面所有return都是無效的,return還有什麼意義呢,所以,實際中finally塊是不允許返回的。eclipse編譯器會提示warning:finallyblock does not complete normally。
幾種情形的測試程式碼
try{ return; }catch(){} finally{} return;
測試程式碼:
1.基本資料型別
public static int test1(){
int a = 10;
try{
a = 20;
return a;
}catch(Exception e){
//other codes.
}finally{
a += 5;
//other codes.
}
return a;
}//最終返回值為20
2.引用資料型別改變引用物件的值
3.引用資料型別改變引用物件public static StringBuffer test2(){ StringBuffer sb = new StringBuffer("abc"); try{ return sb; }catch(Exception e){ //other codes. }finally{ sb.append("DEF"); //other codes. } return sb; }//最終返回中內容為abcDEF
public static StringBuffer test3(){
StringBuffer sb = new StringBuffer("abc");
try{
return sb;
}catch(Exception e){
//other codes.
}finally{
sb = new StringBuffer("DEF");
//other codes.
}
return sb;
}//最終返回值中的內容為abc
在此種情形的測試中可以發現,在finally之前若有return語句,finally對返回變數無論做什麼修改,變數本身是不會再變的,比如為基本型別的變數重新賦值,或是為引用型別的變數重新指定引用都是不會記錄到返回值中的。但是finally中的語句會執行,那麼在finally中對引用型別的變數指向的物件內容是可以修改的,且修改有效。此種情形可以這樣理解,遇到return語句,虛擬機器為return的值蓋了一套房子,房子能隨便拆嗎?不能。但是房子裡面的人是可以變的。基本資料型別和引用型別的變數本身就是房子,引用型別變數指向的物件中的內容就是房子中的人。
有這樣一個小測試:
public static int test(int num){
int a = 10;
try{
return a;
}catch(ArithmeticException e){
a = 20;
return a;
}finally{
a = 30;
return a;
}
}
1.1 try 塊中沒有異常時返回值是多少?
1.2 try塊中有ArithmeticException異常時返回值是多少?
1.3 try塊中有其他異常時返回值是多少?
答案:全部是30.原因是在執行完try塊中return之前的程式碼後,jvm在return之間插入了finally塊中的程式碼,finally塊中含有return,那麼就直接返回了。