首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >再谈try{ return }finally{}中的return?

再谈try{ return }finally{}中的return?

作者头像
烟雨平生
发布2023-03-07 14:10:51
发布2023-03-07 14:10:51
2.2K0
举报
文章被收录于专栏:数字化之路数字化之路

看到之前整理的一篇文章,觉得蛮经典的。 重新整理,并补充返回值是引用类型的case。 先抛个问题热热身:

try{}return引用类型时,finally{}对返回值进行更改。 这是否会改变Function的返回值呢?

一个疑问

谁能给我我解释一下这段程序的结果,为什么是1001而不是1002

代码语言:javascript
复制
    private int tryWithReturnAndFinallyWithReturnPrimitiveType() {
        int result = 1000;
        try {
            result = result + 1;
            log.info("try block result={}", result);
            return result;
        } finally {
            log.info("finally block before result={}", result);
            result = result + 1;
            log.info("finally after block result={}", result);
        }
    }

看了问题后,得出了以下几个问题:

  • 如果在 try 语句块里使用 return 语句,那么 finally 语句块还会执行吗?(如果你的答案是不会执行,请务必要看下去 ^_^)
  • 如果执行,那么是怎样实现既执行 return 又执行 finally 的呢?(如果你的答案是不知道,请继续看下去!!)
  • 上面的程序输出为什么是1001?( 如果不知道,继续看下去~~)
  • 在网上看到还有人还问“是先执行return还是先执行finally?”的 (个人觉得,如果知道finally会执行就可以得出是,先执行finally再执行return的。因为,如果先执行return,那么整个函数都跳出了,那么还怎么执行finally?^_^)

刚看到这个问题后。突然发现基础不够扎实,居然连第一个都答不出来。。。(不知道还有木有和我一样也回答不出以上的问题的?如果有请在评论里告诉我一声,让我知道,我并不孤单~~)

根据已有的知识知道: return 是可以当作终止语句来用的,我们经常用它来跳出当前方法,并返回一个值给调用方法。然后该方法就结束了,不会执行return下面的语句。 finally :无论try语句块发生了什么,不管是抛出异常还是正常执行。finally语句块都会执行。 那么问题来了: 在try语句中使用return后,finally是否还会执行? finally一定会执行的说法是否还成立? 如果成立,那么先执行return还是先执行finally?

验证 finally 语句块是否会执行,以及 return 和 finally的执行顺序

在求知欲的驱动下,我继续进行更深的探索,果断打开了Oracle的主页,翻阅了java 官方教程的finally语句块。发现了官方教程对这个特殊情况有说明:

The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.

简单翻译:

当try语句块退出时肯定会执行finally语句块。这确保了即使发了一个意想不到的异常也会执行finally语句块。但是finally的用处不仅是用来处理异常——它可以让程序员不会因为return、continue、或者break语句而忽略了清理代码。 把清理代码放在finally语句块里是一个经典实践,即便可能不会有异常发生也要这样做。 注意,当try或者catch的代码在运行的时候,JVM退出了。那么finally语句块就不会执行。同样,如果线程在运行try或者catch的代码时被中断了或者被杀死了(killed),那么finally语句块可能也不会执行了,即使整个应用还会继续执行。

从上面的官方说明,我们知道无论try里执行了return语句、break语句、还是continue语句,finally语句块还会继续执行。 同时,在stackoverflow里也找到了一个答案,我们可以调用System.exit()来终止它:

finally will be called. The only time finally won't be called is: if you call System.exit(), another thread interrupts current one, or if the JVM crashes first.

另外,在java的语言规范有讲到,如果在try语句里有return语句,finally语句还是会执行。它会在把控制权转移到该方法的调用者或者构造器前执行finally语句。 也就是说,使用return语句把控制权转移给其他的方法前会执行finally语句。

part1:

验证基本数据类型:

代码语言:javascript
复制
    /**
     * 返回值 为java基本数据类型
     * finally语句块对try语句块返回值的更改无效
     */
    @Test
    public void try_with_return_and_finally_with_return_primitive_type() {
        int result = tryWithReturnAndFinallyWithReturnPrimitiveType();
        assertThat(result).isEqualTo(1001);
    }

    private int tryWithReturnAndFinallyWithReturnPrimitiveType() {
        int result = 1000;
        try {
            result = result + 1;
            log.info("try block result={}", result);
            return result;
        } finally {
            log.info("finally block before result={}", result);
            result = result + 1;
            log.info("finally after block result={}", result);
        }
    }

这个case的运行结果如下:

可以看到finally{}对try{}返回值的修改并没有改变Function的返回值

为什么是1001不是1002呢?

官方的jvm规范把一切的谜团解开了:

If the try clause executes a return, the compiled code does the following:

  1. Saves the return value (if any) in a local variable.
  2. Executes a jsr to the code for the finally clause.
  3. Upon return from the finally clause, returns the value saved in the local variable.

简单翻译:

如果try语句块里有return,那么代码的行为如下: 1.如果有返回值,就把返回值(如果存在)保存到局部变量中 2.执行jsr指令跳到finally语句块里执行 3.执行完finally语句块后,返回之前保存在局部变量表里的值

根据上面的说明就可以轻易地解释为什么是1001了。当执行到return result;时,jvm会在局部变量表里另外分配一个空间来保存当前result的值。注意,现在还没把值返回给调用方,而是继续执行finally{}里的语句。等执行完后再把之前保存的值(是1001不是result变量)作为Function的返回值。所以就有了Function返回值是1001不是1002的情况。

其实这里还有一点要注意的是,如果你在finally里也用了return语句,比如return result。那么调用方收到的值会是1002。 因为规范规定了,当try和finally里都有return时,会忽略try的return,而使用finally的return

小结:

https://www.processon.com/view/link/5e855502e4b0a2d870289d53

part2:

验证基本数据类型的包装类:

上面case验证的是Java中的基本数据类型。如果是基本数据类型的包装类呢?

验证引用类型:

如果是返回值是引用类型呢?

(1)JDK自带的引用类型:

(2)自定义的引用类型:

小结: (1)不管try{}、catch{}中是否有return语句,finally{}都会执行,除非未执行到finally{}时,执行当前Function的Thread被interruptedkilled

(2)finally{}中没有return语句时, 如果try{}或catch{}返回值是基本数据类型或其包装类时,finally{}对返回值的更改,不会返回给调用方;

如果try{}或catch{}返回值是引用数据且不是基本类型的包装类时,finally{}对返回值的更改会返回给调用方; (2)finally{}中的return语句会的覆盖之前try{}、catch{}中的return语句; (3)如果finally{}中包含了return语句,即使前面的catch{}重新抛出了异常,则调用该方法的语句也不会获得catch{}重新抛出的异常,而是会得到finally{}的返回值,并且不会捕获异常。

引用: https://www.cnblogs.com/averey/p/4379646.html

文中用到的case: https://github.com/helloworldtang/spring-boot-cookbook/blob/master/learning-demo/src/test/java/com/tangcheng/learning/syntax/TryFinallyWhenReturnTest.java

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 验证 finally 语句块是否会执行,以及 return 和 finally的执行顺序
    • 验证基本数据类型:
  • 为什么是1001不是1002呢?
    • 验证基本数据类型的包装类:
    • 验证引用类型:
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档