java 判断顺序_通过指令码来判断Java代码的执行顺序(++问题与return和finally的问题)...
問題
在《深入理解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文件中就包含著程序代碼最終的執行順序。
我們可以通過官方提供的javap -c 再加上class文件的路徑來得到各個方法對應的指令碼。
例如:javap -c Test.class
引例
由于是打算使用JVM的指令碼來解決這個問題,剛開始先以一個簡單的方法來說明一下。對于如下方法:
public int method1() {
int i = 1;
return i;
}
該方法對應的指令碼為:
public int method1();
Code:
0: iconst_1
1: istore_1
2: iload_1
3: ireturn
每個指令對應著一個操作,上面的指令碼意思是:
將int型數值0推送至棧頂
將棧頂int型元素存入第二個空間中
將第二個空間的int型元素推送至棧頂
返回將棧頂的int型元素并退出這個方法
由此可以看出,通過指令碼,我們可以直觀地看到程序代碼的執行順序,這對于解決任何執行順序的問題是一個利器。
如果還是感覺有些不明所以,那我們可以再看看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為例:
將int型數值0推送至棧頂
將棧頂int型元素存入第二個空間中
將第二個空間的int型元素(1)推送至棧頂
將第二個空間的int數據+1后再放回第二個空間
返回將棧頂的int型元素并退出這個方法
需要注意的是,第三步是將1而不是整個空間推送至棧頂,所以第四步對第二個空間中的數據1加1后并沒有改變棧頂的值,因此返回值為1。相對的,method2則是:
將int型數值0推送至棧頂
將棧頂int型元素存入第二個空間中
將第二個空間的int數據+1后再放回第二個空間
將第二個空間的int型元素(2)推送至棧頂
返回將棧頂的int型元素并退出這個方法
所以,返回的是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語句塊的指令碼已經包含在里面了:
將int型數值1推送至棧頂
將棧頂int型元素存入第二個空間中
將第二個空間的int型元素(1)推送至棧頂
將棧頂int型元素存入第五個空間中
將int型數值3推送至棧頂
將棧頂int型元素存入第二個空間中(3)
將第五個空間的int型元素(1)推送至棧頂
返回將棧頂的int型元素并退出這個方法
由此可以看出,方法返回的是第五個空間的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語句塊。
筆記內容只是本人思考而寫,如果有什么問題,還請指出,謝謝!
總結
以上是生活随笔為你收集整理的java 判断顺序_通过指令码来判断Java代码的执行顺序(++问题与return和finally的问题)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php开发添加表情功能,WordPres
- 下一篇: OC 中NSString与NSStrin