我在书中遇到如下代码《深入理解Java虚拟机》:
公共 int 方法() {
int i;
尝试一下{
我 = 1;
return我;
} catch (异常 e) {
我 = 2;
return我;
}终于{
我= 3;
}
}
搜索了关于return andfinally的问题后,只是看到finally会被执行,这导致我误认为我只是把finally的执行顺序放在了return的语句之前,所以判断了这段代码的执行。结果应该是3,但实际结果是1,研究了一下,发现一开始确实很困惑,所以记录下来。
我们都知道class文件中的内容是JVM可以理解的字节码。 JVM也是根据类的字节码来执行程序代码的,因此类文件中包含了程序代码的最终执行顺序。
我们可以通过官方的javap -c加上class文件的路径得到每个方法对应的指令代码。
例如:javap -c Test.class
由于我们打算使用JVM指令代码来解决这个问题,所以我一开始就简单的解释一下。对于以下方法:
public int method1() {
int i = 1;
return我;
}
该方法对应的命令代码为:
public int method1();
代码:
0:iconst_1
1:istore_1
2:iload_1
3:伊return
每条指令对应一个操作。以上指令代码的意思是:
由此可见,通过指令代码,我们可以直观地看到程序代码的执行顺序,是解决任何执行顺序问题的有力工具。
如果你还觉得有点不清楚,那么我们可以看看i++
和++i
的问题。对于以下代码:
//return1
public int method2() {
int i = 1;
returni++;
}
//return2
public int method3() {
int i = 1;
return++i;
}
他们的命令代码是:
public int method2();
代码:
0:iconst_1
1:istore_1
2:iload_1
3: iinc 1, 1
6:return
public int method3();
代码:
0:iconst_1
1:istore_1
2: iinc 1, 1
5:iload_1
6:伊return
显然,这两个指令代码最大的区别在于iinc 1,1
指令的位置不同,如果删除这条指令,它将与method1
一致,对应源码,这条指令是符号++
的影响。
而这个按键的功能iinc 1,1
指令即使你完全不懂也能猜到。就是将第二个空间的int数据+1,然后放回第二个空间。空间。
把这个意思放到命令码里再写一遍。以方法2
为例:
需要注意的是,第三步是压入1而不是整个空间到栈顶,所以第四步在栈顶加1后并不改变栈顶。第二个空格中的数据1。 value,所以返回值为1。相比之下,method2
是:
所以,返回值为2。
现在我们可以看看原来的方法
方法。再次复制代码:
公共 int 方法() {
int i;
尝试一下{
我 = 1;
return我;
} catch (异常 e) {
我 = 2;
return我;
}终于{
我= 3;
}
}
对应命令码:
public int method();
代码:
0:iconst_1
1:istore_1
2:iload_1
3:istore 4
5:iconst_3
6:istore_1
7:iload 4
9:伊return
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:伊return
21:astore_3
22:iconst_3
23:istore_1
24:aload_3
25:投掷
异常表:
从 到 目标类型
0 5 10 类 java/lang/Exception
0 5 21 任意
10 16 21 任意
这个命令代码的不同之处在于最后有一个异常表。我们暂时不关心它。我们先看第一个ireturn
命令的命令代码,也就是代码中的第9行。脚本代码:
0:iconst_1
1:istore_1
2:iload_1
3:istore 4
5:iconst_3
6:istore_1
7:iload 4
9:伊return
该命令码是无异常情况下程序执行的命令码。已经包含finally语句块的命令代码:
可以看到该方法在第五个空间返回1,而不是在第二个空间返回3,与运行结果一致。
其中,重点是第四步和第七步。可见,当Java程序在执行过程中遇到return语句时,会首先保存方法的返回值。如果有finally语句块,那么会先执行finally语句块,最后取出返回值。返回。
另外,如果return后面跟的是表达式或者方法,会先计算最终的返回值,然后再执行finally语句块,这个可以自行验证。
当然,如果保存的返回值是引用类型变量,那么在finally代码块中修改就会改变变量本身的属性,从而改变返回值的属性。毕竟finally的代码确实执行了。 。
比如返回一个List,在finally中添加或删除该List,返回的List的内容自然会发生变化。
关于剩下的指令代码,涉及到的知识较多,所以我就根据自己的理解简单说一下。
该指令代码末尾有异常表。其含义可以简单解释为:在[从,到)的区间内,如果出现type类型的异常,则跳转到目标 执行。
因为异常表的存在,当异常发生时,程序可以根据产生的异常跳转到正确的位置执行下一段代码。
[10,20]是catch代码块对应的指令代码,但是捕获到的异常会被存储,也就是源码中的Exception e
。 [21,25]会保存try语句块中抛出的未被catch捕获的异常,然后执行finally代码,最后抛出异常结束方法。
这三段指令代码都包含了finally指令代码,保证了源码中的finally代码一定会被执行。
Java程序在执行过程中遇到return语句时,会首先保存方法的返回值。如果有finally语句块,那么会先执行finally语句块,最后取出返回值返回。另外,如果return后面跟的是表达式或者方法,那么会先计算最终的返回值,然后再执行finally语句块。
笔记内容只是我自己的想法和写作。如有疑问,请指出,谢谢!
-->