Java异常体系中finally和return执行顺序的反编译分析详解

问题

Java异常体系中,在try块或catch块内使用return时,可能会出现出乎开发人员预料的结果,引发问题的主要原因是对finally和return的执行顺序的不熟悉,本文将对相关代码进行反编译,分析其执行顺序。

先看下列一段代码:

	public class Test {

    public static void main(String[] args) {
        System.out.println("main i = " + test());
    }

    public static int test(){
        int i = 100;
        try{
            i = 200;
            System.out.println("try i = " + i);
            return i = 300;
        }finally {
            i = 400;
            System.out.println("finally i = " + i);
        }
    }

}

执行结果如下:

try i = 200
finally i = 400
main i = 300

执行结果可能会不符合部分开发人员的预期,因为相信在开发人员学习异常体系的时候,都会听过finally在最后必定执行这句话,从执行结果来看finally块也的确被安排在最后执行,但却没能改变返回结果,引出了本篇博文想要深入探讨的问题,即finally块和return语句的执行顺序。

分析

try块/catch块有return

接下来对Test测试类进行javap反编译结果分析,在分析前,先简单对方法进行简化,以使得反编译结果聚焦在要讨论的问题上,简化后代码如下:

public class Test {

    public static void main(String[] args) {
        test();
    }

    public static int test(){
        int i = 100;
        try{
            i = 200;
            return i = 300;
        }finally {
            i = 400;
        }
    }

}

test()方法反编译结果如下,结果中会添加注释:

public static int test();
    Code:
       0: bipush        100		//将100送入操作数栈顶
       2: istore_0				//将栈顶int型数值(100)存入第0个本地变量
       3: sipush        200		//将200送入操作数栈顶
       6: istore_0				//将栈顶int型数值(200)存入第0个本地变量 此时第0个本地变量被覆盖 现值为200
       7: sipush        300		//将300送入操作数栈顶
      10: dup					//复制栈顶数值并将复制值压入栈顶 此时栈内两个300
      11: istore_0				//将栈顶int型数值(300)存入第0个本地变量 此时第0个本地变量被覆盖 现值为300
      12: istore_1				//将栈顶int型数值(300)存入第1个本地变量
      13: sipush        400		//将400送入操作数栈顶
      16: istore_0				//将栈顶int型数值(400)存入第0个本地变量 此时第0个本地变量被覆盖 现值为400
      17: iload_1				//将第1个本地变量(300)送到栈顶 
      18: ireturn				//从当前方法返回int
      19: astore_2
      20: sipush        400		//将400送入操作数栈顶
      23: istore_0				//将栈顶int型数值(400)存入第0个本地变量
      24: aload_2
      25: athrow

结果中出现过的JVM指令说明如下,说明中所提及的栈均为操作数栈:

指令说明
bipush将单字节的常量值(-128~127)推送至栈顶
sipush将一个短整型常量值(-32768~32767)推送至栈顶
istore_x将栈顶int型数值存入第x个本地变量
iload_x将指定的第x个int型本地变量推送至栈顶
astore_x将栈顶引用型数值存入第x个本地变量
aload_x将第x个引用类型本地变量推送至栈顶
ireturn从当前方法返回int
athrow将栈顶的异常抛出
dup复制栈顶数值并将复制值压入栈顶

根据上文反编译结果分析可见指令流程大体如下:

  1. 执行return前代码:0~6行执行i = 100; i =200;
  2. 执行return语句注意是此处是执行return语句,并非返回操作,7~12行执行i = 300;
  3. 执行finally语句:13~16行执行i = 400;
  4. 执行return返回操作:17~18行返回栈顶int型数值,将数据送至栈顶时,推送的本地变量是return语句最后保存的本地变量,而非finally块内最后保存的本地变量。

至此可以清楚的弄清,finally和return的执行顺序是

  1. return语句执行。
  2. finally执行。
  3. return执行返回。finally内修改return语句将要返回的对象并不实际影响返回结果。

catch块中存在return时情况与try块完全一致,finally的内容也无法实际影响catch块中的return返回。

当出现try块和catch块都有return语句时,程序照常运行,出现异常时执行catch,无异常执行try块。

try块和finally块都有return

现将代码修改如下,以模拟标题所述情况:

public class Test {

    public static void main(String[] args) {
        test();
    }

    public static int test(){
        int i = 100;
        try{
            i = 200;
            return i = 300;
        }finally {
            i = 400;
            return i;
        }
    }
}

上面一段代码test()方法反编译结果如下,重复部分不做冗余注释:

public static int test();
    Code:
       0: bipush        100
       2: istore_0
       3: sipush        200
       6: istore_0
       7: sipush        300
      10: dup
      11: istore_0			//try块中的300保存在本地变量0
      12: istore_1			//try块中的300保存在本地变量1
      13: sipush        400
      16: istore_0			//finally块中的400保存在本地变量0 覆盖300
      17: iload_0			//将本地变量0(400) 送入栈顶
      18: ireturn			//栈顶返回
      19: astore_2
      20: sipush        400
      23: istore_0
      24: iload_0
      25: ireturn

通过反编译结果可见17行在返回之前从送到操作数栈顶的变量是finally中最后修改过的值。

由此可以得出结论,当try/catch块和finally块中同时存在return语句时,实际执行的是finally块中的return语句。

相信仔细阅读的读者在以上的反编译结果也会发现一个问题,就是为什么看似finally块在字节码中执行了两次,尤其是为什么会有两个ireturn指令。对此笔者也是深受困扰,四下搜索也没有结果,如若有读者了解还请赐教纠正。

总结

文章验证后结论如下:实际执行时,如果finally中没有return,则执行顺序如下:

  1. 执行return语句。
  2. 执行finally。
  3. 执行返回操作,finally对于返回无影响。

如finally中有return,则最终一定执行finally中的return语句。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值