【Java学习】理解try{ return } finally {} 中执行顺序

2024-08-31 13:38

本文主要是介绍【Java学习】理解try{ return } finally {} 中执行顺序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

先看一段代码:


public class testFinally {public int test() {int x = 1;try{return ++x;}catch(Exception e){}finally{++x;}return x;}public static void main(String[] args) {testFinally t = new testFinally();int y = t.test();System.out.println(y);}
}

对于以上代码有以下几个问题:

  • 如果在 try 语句块里使用 return 语句,那么 finally 语句块还会执行吗?
  • 如果执行,那么是怎样实现既执行 return 又执行 finally 的呢?
  • 上面的程序输出为什么是2?

根据已有的知识知道:
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语句。

个人验证

在int x = 1; 一行加上断点,开始debug:
这里写图片描述
按F6,单步执行:
这里写图片描述

执行到try中的return语句,x此时还没有进行++操作,所以仍为1。
再继续执行:
这里写图片描述

程序此时并没有直接返回结果,而是执行到了finally语句中,由于return一句已经执行了++x,所以此时x值为2.
继续执行:
这里写图片描述

我看看到,执行完finally的语句后,程序又跳转回了return语句,并且此时x的值已经变成了3.
继续执行:
这里写图片描述

可以看到传给y的值确实是2。

从上面过程中可以看到:

  • 在 try 里 使用 return 还是会执行finally语句的(我们用debug的模式看到了程序会条件 finally语句里执行)
  • 执行完finally语句才执行 return。为什么?从上面的图可以合理推理出return ++x;是分开来执行的,先执行++x,再执行finally,最后才执行return跳出函数。因为程序调两次跳到了 return ++x; 语句上。(其实要验证 return ++x 是分开两部分执行的方法很简单,把变量x变成static变量并在main函数里输出,会发现x的值还是3,即使两次跳到 return ++x 也只是第一次执行了加1操作,第二次只是执行了return而没有执行++x。这里是合理推理,后面有真凭实据~~)

看到这,我们可能会再次纠结起来了。从上面的验证可以看出,finally语句执行了,而且x的值也确实加到3了,那么为什么y是2呢?

验证为什么是2不是3

翻看官方的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语句后,返回之前保存在局部变量表里的值

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

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

原文中还有对于这个问题用字节码的解释(图文并茂,很清楚),感兴趣的同学可以查看原文。
博主:孤光
原文链接:http://www.cnblogs.com/averey/p/4379646.html
感谢大佬!!

这篇关于【Java学习】理解try{ return } finally {} 中执行顺序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124055

相关文章

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e