在《深刻理解Java虛擬機》一書中遇到了以下代碼:java
public int method() { int i; try { i = 1; return i; } catch (Exception e) { i = 2; return i; } finally { i = 3; } }
因爲曾經搜了一下return和finally的問題後,只是簡單的看到了finally會執行,從而致使本身誤覺得只是簡單地把finally的執行順序放到return語句以前,所以判斷這段代碼的執行結果應該是3,可實際運行結果是1。研究後發現本身當初真是太糊塗,因而便記錄下來。工具
咱們都知道,class文件中的內容就是可供JVM理解的字節碼,JVM也是根據class的字節碼來執行程序代碼,因此class文件中就包含着程序代碼最終的執行順序。code
咱們能夠經過官方提供的javap -c 再加上class文件的路徑來獲得各個方法對應的指令碼。get
例如:javap -c Test.class
虛擬機
因爲是打算使用JVM的指令碼來解決這個問題,剛開始先以一個簡單的方法來講明一下。對於以下方法:io
public int method1() { int i = 1; return i; }
該方法對應的指令碼爲:table
public int method1(); Code: 0: iconst_1 1: istore_1 2: iload_1 3: ireturn
每一個指令對應着一個操做,上面的指令碼意思是:ast
由此能夠看出,經過指令碼,咱們能夠直觀地看到程序代碼的執行順序,這對於解決任何執行順序的問題是一個利器。class
若是仍是感受有些不明因此,那咱們能夠再看看i++
和++i
的問題。對於以下代碼:變量
// return 1 public int method2() { int i = 1; return i++; } // return 2 public int method3() { int i = 1; return ++i; }
它們的指令碼分別是:
public int method2(); Code: 0: iconst_1 1: istore_1 2: iload_1 3: iinc 1, 1 6: ireturn public int method3(); Code: 0: iconst_1 1: istore_1 2: iinc 1, 1 5: iload_1 6: ireturn
顯然,這兩段指令碼最大的區別就是iinc 1,1
指令的位置不一樣,並且若是把這條指令刪除,那麼與method1
的指令碼徹底一致,對應源代碼來看,這條指令就是++
這個符號的影響了。
而這個關鍵的iinc 1,1
指令的做用哪怕徹底不懂也能猜出來,就是將第二個空間的int數據+1後再放回第二個空間。
將這個含義放到指令碼中再從新捋一遍,以method2
爲例:
須要注意的是,第三步是將1而不是整個空間推送至棧頂,因此第四步對第二個空間中的數據1加1後並無改變棧頂的值,所以返回值爲1。相對的,method2
則是:
因此,返回的是2。
如今咱們能夠看最初的method
方法了,在這裏再複製一遍代碼:
public int method() { int i; try { i = 1; return i; } catch (Exception e) { i = 2; return i; } finally { i = 3; } }
對應的指令碼:
public int method(); Code: 0: iconst_1 1: istore_1 2: iload_1 3: istore 4 5: iconst_3 6: istore_1 7: iload 4 9: ireturn 10: astore_2 11: iconst_2 12: istore_1 13: iload_1 14: istore 4 16: iconst_3 17: istore_1 18: iload 4 20: ireturn 21: astore_3 22: iconst_3 23: istore_1 24: aload_3 25: athrow Exception table: from to target type 0 5 10 Class java/lang/Exception 0 5 21 any 10 16 21 any
這段指令碼不一樣的地方在於最後有一個異常表,咱們先不用管它,先看到第一個ireturn
指令的指令碼,即代碼中的第9行爲止的指令碼:
0: iconst_1 1: istore_1 2: iload_1 3: istore 4 5: iconst_3 6: istore_1 7: iload 4 9: ireturn
這段指令碼就是當沒有異常時,程序執行的指令碼,finally語句塊的指令碼已經包含在裏面了:
由此能夠看出,方法返回的是第五個空間的1而不是第二個空間的3,和運行結果一致。
其中,關鍵的地方就是第四步以及第七步。因而可知,Java程序在執行時遇到return語句時,會先將方法的返回值保存起來,若是還有finally語句塊,那麼就先執行finally語句塊,最後再將返回值取出後返回。
另外,若是return後跟的是表達式或者方法,那麼會先計算出最終的返回值後再執行finally語句塊,可自行驗證。
固然,若是保存的返回值是一個引用類型的變量,那麼在finally代碼塊中修改則會改變這個變量自己的屬性,於是改變返回值的屬性,畢竟finally的代碼是的的確確執行過了。
例如,返回一個List,在finally中又對List進行了增長或刪除,那麼返回的List的內容天然也變了。
關於指令碼其他的部分,涉及到更多知識,在這裏根據個人理解簡單說一下。
這段指令碼最後有一個異常表,它的含義能夠簡單解釋爲:在[from,to)的區間內,若是發生type類型的異常,那麼就跳到target執行。
正由於有了異常表的存在,在出現異常時,程序能夠根據產生的異常來跳到正確的位置執行接下來的代碼。
[10,20]即爲catch代碼塊對應的指令碼,不過其中會把捕捉到的異常存儲下來,也就是源代碼中的Exception e
。[21,25]則是會把try語句塊中拋出的catch沒有捕捉的異常保存下來,而後執行finally的代碼,最後拋出該異常結束方法。
這三片指令碼都包含了finally的指令碼,也就保證了源代碼中finally的代碼確定會執行。
Java程序在執行時遇到return語句時,會先將方法的返回值保存起來,若是還有finally語句塊,那麼就先執行finally語句塊,最後再將返回值取出後返回。另外,若是return後跟的是表達式或者方法,那麼會先計算出最終的返回值後再執行finally語句塊。
筆記內容只是本人思考而寫,若是有什麼問題,還請指出,謝謝!