finally不是一定会执行

在网上看到的帖子,基本上都是说 finally 一定会执行,开始我也一直是这么认为的。但后来我对这块内容做了测试,也看了一些文章,发现 finally不是一定会执行的

1,结论

finally不一定会执行,只有与 finally 相应的try代码块得到执行的情况下,finally 才会执行!!

2,实例与分析

2,1 finally没有执行的情况

下面的示例代码中,有finally代码块,但结果是 finally 的代码没有被执行,内容没有打印出来。

    public static int test() {
        int i = 1;
        System.out.println("test");
        // 在这里会抛出异常,finally不会执行
        i = i / 0;

        try {
            // 若把i = i / 0语句放在try中,finally就会执行,进行了finally对应的try代码块中
            // i = i / 0;
            System.out.println("try code");
        } finally {
            System.out.println("finally code");
        }
    }

就像上面的这种情况,在 try 代码块前已经出现了异常。在此后的代码都不会执行,finally 自然也不会执行。开始的结论说了,finally 不一定会执行,只有与 finally 相应的try代码块得到执行的情况下,finally 才会执行!!代码中的异常是在 try 代码前出现的,会导致 try 代码块得不到执行,最终 finally 也得不到执行。

2,2 finally的执行原理

finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把
程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。

实际上,JVM会把 finally 语句块作为 subroutine(可简单为翻译为子程序)直接插入到 try 语句块或者 catch 语句块的控制
转移语句之前
。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch
语句块会保留其返回值到本地变量表(Local Variable Table)中。等 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)

前面曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。  

上面的说明,对应实现情况,就是如果 finally 语句中没有 return 语句覆盖返回值,那么原来的返回值可能因为 finally 里的修改而改变,也可能不变。

finally 没有改变返回值的情况

    public static int test55() {
        int b = 20;
        try {
            System.out.println("try block");
            return b += 80;
        } catch (Exception e) {
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");

            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b = 150;
            System.out.println("finally, b =" + b);
        }
        return 2000;
    }

这里的结果是100,没有受到 finally 代码块的影响。并不是没有执行 finally 的代码,可以打断点一步步执行。发现开始执行了return b += 80这一行代码,此时 b = 100,在 finally中,b 也确实是150了。但执行完System.out.println("finally, b =" + b)后又跳到了return b += 80这行代码,所以结果还是100。相当于return b += 80执行了两次。

由此也说明,在 finally 代码块前有 return 且 finally 会执行的情况下

第一步:return先执行了一次,然后将其返回值保留到本地变量表(Local Variable Table)中;

第二步:执行 finally代码块的内容;

第三步:执行完 finally代码块后,再次来到最终的return,将其返回给该方法的调用者(invoker)。

finally 改变了返回值

    public static Map<String, String> getMap() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("KEY", "INIT");

        try {
            map.put("KEY", "TRY");
            return map;
        }
        catch (Exception e) {
            map.put("KEY", "CATCH");
        }
        finally {
            map.put("KEY", "FINALLY");
            map = null;
        }
        return map;
    }

这里的返回结果是 FINALLY。执行了 finally 代码块,map确实也是为 null 了。为什么呢?还是按照上面说的三步来分析。

第一步,第一次执行 return map时,此时 map 中的 value 是 TRY。在JVM中,将返回值 map 保留到本地变量表中。

第二步,然后执行了 finally代码块,先是将修改了 map 的内容,将键为 KEY 值为 TRY 修改成了键为 KEY 值为 FINALLY,又将 map 的引用设置为 null。要说明的一点是,其实创建的Map对象还是内存中存在(位于堆中),虽然在 finally 最后中将 map 的引用设置为 null了,但并没有影响到堆中Map对象的内容。此时堆中Map对象的 value 还是 FINALLY。

第三步,finally 代码块执行结束后,再来执行return map,此时会取出之前保存的 map,所以这里的 map 不是null,就是指向了堆中Map对象的内容。还是有一点要注意的是,这里是要返回一个Map对象,有点特殊,而不是返回一个String类型的变量,若只是一个String,那是应该返回 TRY,这里是根据 KEY 来得到它对应的值,而在堆中的Map对象中根据键为 KEY 得到的值就是 FINALLY。

有异常的情况下

    public static int test44() {
        int b = 20;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 80;
        } catch (Exception e) {
            b += 15;
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b += 50;
        }
        return 204;
    }

这里返回是204,这个很好理解,在 return b += 80前的一行 b = b / 0报错了,那 return b += 80这一行就不会执行,而是直接进入 catch 代码块执行,再进入 finally 块执行,在 finally 中的b = 85,finally 执行完后,就运行到最后一行返回204。若最后一行是返回的 b,那此时就是85。这里之所以好分析好理解,是因为 return在最后,顺序上很自然,而上面的两个例子的 return 在前面,最终还是跳到去。

总结

若 try 语句没有得到执行(比如在 try 前抛出了异常或返回),或在finally代码块前JVM已经终止(比如调用了System.out(0)),那 finally 则不会执行。其余情况下finally会执行。

另外,若方法有返回结果,finally得到执行但没有返回结果时,会使用方法最后一行的返回结果或者再次执行之前最后执行过的返回行代码(如果在方法最后一行前有返回行且得到了执行的情况下)。若finally中有返回结果,以finally中的返回结果为最终结果。

finally 语句块是在 try 或者 catch 或finally 或其它地方(如最后一行有return)中的 return 语句之前执行的。下面分情况讨论有catch与没有catch的情况:
    1,有 catch 时,首先当有异常时才会执行 catch;
          a,若在 catch 中抛出异常,那会先执行 finally;
          b,若 catch 中有 return 则最终结果是 catch 中的 return,
          c,但若同时 finally 中也有 return 的话,不管有没有异常,最终结果都是 finally 中的 return。若没有异常则不会执行到                             catch。
    2,没有 catch 时,即只有 try 和 finally。不管有没有异常自然也不执行到 catch,因为就没有 catch 代码。
          a,若有异常时,
                若异常出现在 try 代码前(没有被捕获),则异常处后不再执行,finally 也不会执行
                      (即只有与 finally 相应的 try 语句块得到执行的情况下,finally 语句块才会执行);
                若异常出现在 try 中,则会执行 finally;
          b,若没有异常,
                 当 finally 中有 return 时,则最终结果是 finally 的 return;
                 当 finally 中没有 return 时,则以其它的 return 为最终结果。

参考内容

1,关于Java中finally语句块的尝试辨析IBM Developer 正在整合其语言站点组合。 – IBM Developer

2,Java finally语句到底是在return之前还是之后执行?Java finally语句到底是在return之前还是之后执行? - 知乎

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值