java中 try catch finally和return联合使用时,代码执行顺序的小细节
代码1测试
public static void main(String[] args) {
aa();
}
static int aa() {
try {
int a=4/0;
} catch (Exception e) {
e.printStackTrace();
return 1;
}finally{
System.out.println("finally");
// return 2; return写在这里不规范,不是错误,但有感叹号
}
return 2;
}
输出结果如下:
java.lang.ArithmeticException: / by zero
at com.hcss.cn.Aa1fa.aa(Ja.java:120)
at com.hcss.cn.Aa1fa.main(Ja.java:115)
finally
分析:虽然try中出现了运行异常java.lang.ArithmeticException,被catch捕获到,不管程序是正常运行,还是抛异常,之前都要先调用finally(这里想说下多次执行程序时控制台输出信息顺序会变,是因为e.printStatckTrace和System.out是两个资源,并行执行,但代码2我都用成System.out,多次输出也会偶尔出现乱序,没搞明白,先不管这个小问题了),finally正确使用应该是释放资源,如果finally用了return,这是不规范的写法。
稍微变形的代码2测试(catch的异常类变了)
public static void main(String[] args) {
System.out.println(aa());
}
static int aa() {
try {
int a=4/0;
} catch (Error e) {
System.out.println("catch...");
return 1;
}finally{
System.out.println("finally...");
}
//执行完finally,因为没有catch住ArithmeticException异常,导致异常向上层main方法抛出,下边两行system.out没有输出
System.out.println("finally1...");
System.out.println("finally2...");
return 2;
}
输出结果如下:
finally...
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.hcss.cn.Aa1fa.aa(Ja.java:120)
at com.hcss.cn.Aa1fa.main(Ja.java:115)
分析:
执行完finally,因为没有catch住ArithmeticException异常,导致异常向上层main方法抛出,下边两行system.out没有输出。
再变形代码3测试(return写在了finally里)
public static void main(String[] args) {
System.out.println(aa());
}
static int aa() {
try {
int a=4/0;
} catch (Error e) {
System.out.println("catch...");
return 1;
}finally{
System.out.println("finally...");
return 2; //return写在这里不规范,有感叹号
}
}
输出结果如下:
finally...
2
分析
finally用了return,这是不规范的写法,不应该这么使用,finally中的return使得aa方法丢失了要抛出的异常,相当于aa方法没有向main方法抛出异常,而是只返回一个2。 而后,从网上找到一篇文章,看完又学到一点原理性的小知识(重点是return返回的是一个快照值):
一些准备知识:
首先为了说明白java中finally与return的执行顺序是怎样的这个问题,我们需要做一点准备工作。
java方法是在栈幀中执行,栈幀是线程私有栈的单位,执行方法的线程会为每一个方法分配一小块栈空间来作为该方法执行时的内存空间,栈幀分为三个区域:
1. 操作数栈,用来保存正在执行的表达式中的操作数,数据结构中学习过基于栈的多项式求值算法,操作数栈的作用和这个一样
2. 局部变量区,用来保存方法中使用的变量,包括方法参数,方法内部声明的变量,以及方法中使用到的对象的成员变量或类的成员变量(静态变量),最后两种变量会复制到局部变量区,因此在多线程 环境下,这种变量需要根据需要声明为volatile类型
3. 字节码指令区,这个不用解释了,就是方法中的代码翻译成的指令
return语句:
准备知识讲完了,在本节中我们讲解方法中没有finally语句块的情况下return语句的执行方式。现在我们先看看return语句,return语句的格式如下:
return [expression];
其中expression(表达式)是可选的,因为有些方法没有返回值,所以return后面也就没有表达式,或者可以看做是空的表达式。
我们知道return语句的作用可以结束方法并返回一个值,那么他返回的是哪里的值呢?返回的是return指令执行的时刻,操作数栈顶的值,不管expression是一个怎样的表达式,究竟做了些什么工作,对于return指令来说都不重要,他只负责把操作数栈顶的值返回。
而return expression是分成两部分执行的:
执行:expression;
执行:return指令;
例如:return x+y;
这句代码先执行x+y,再执行return;首先执行将x以及y从局部变量区复制到操作数栈顶的指令,然后执行加法指令,这个时候结果x+y的值会保存在操作数栈的栈顶,最后执行return指令,返回操作数栈顶的值。
对于return x;先执行x,x也是一个表达式,这个表达式只有一个操作数,会执行将变量x从局部变量区复制到操作数栈顶的指令,然后执行return,返回操作数栈顶的值。因此return x实际返回的是return指令执行时,x在操作数栈顶的一个快照或者叫副本,而不是x这个值。
finally语句块:
如果方法中有finally语句块,那么return语句又是如何执行的呢?
例如下面这段代码:
try{
return expression;
}finally{
do some work;
}
首先我们知道,finally语句是一定会执行,但他们的执行顺序是怎么样的呢?他们的执行顺序如下:
1、执行:expression,计算该表达式,结果保存在操作数栈顶;
2、执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;
3、执行:finally语句块中的代码;
4、执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
5、执行:return指令,返回操作数栈顶的值;
我们可以看到,在第一步执行完毕后,整个方法的返回值就已经确定了,由于还要执行finally代码块,因此程序会将返回值暂存在局部变量区,腾出操作数栈用来执行finally语句块中代码,等finally执行完毕,再将暂存的返回值又复制回操作数栈顶。所以无论finally语句块中执行了什么操作,都无法影响返回值,所以试图在finally语句块中修改返回值是徒劳的。因此,finally语句块设计出来的目的只是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的。
如果还有不懂的话可以看看示例代码
public class FinallyDemo {
public int testMethod(String _int,String _className){
int x = 1;
try{
Integer.valueOf(_int);
Class.forName(_className);
//如果上面两句代码没有发生异常,对于return x这句代码,程序会先计算表达式x
//即将x从局部变量区复制到操作数栈顶,结果就是操作数栈顶的值,也就是x的值,为1
//然后将操作数栈顶的值复制到局部变量区(假设这个复制到局部变量区的值叫returnvalue),再执行finally代码块,在finally代码块
//中,x的值被修改为3(即局部变量区中的x值),finally执行完,程序又将returnvalue复制到操作数栈顶,然后执行return指令,返回
//操作数栈顶的值,最终返回值是1
return x;
}catch(ClassNotFoundException e){
//同样发生了ClassNotFoundException,x的值被修改成2
//因此在catch中的return x语句中定义了返回值大小为2,所以最终返回的是2
x = 2;
return x;
}finally{
//这句代码一定会执行的,这里的代码执行结束后才会结束方法并返回值,因此在finally语句修改x不会影响返回值,因为返回值在return 后面的
//表达式执行完就已经确定了,他是x的快照,而不是x
x = 3;
}
}
public static void main(String [] args){
try{
FinallyDemo demo = new FinallyDemo();
int i_1 = demo.testMethod("123123", "expert.in.java.lang.CalendarDemo");
System.out.println(i_1);//结果为1
int i_2 = demo.testMethod("123123", "com.edu.cn.qj.MyClass");
System.out.println(i_2);//结果为2
int i_3 = demo.testMethod("dsadf", "expert.in.java.lang.CalendarDemo");
System.out.println(i_3);//没有返回值,会抛出异常
}finally{
}
}
}
---------------------
作者:qj19842011
来源:CSDN
原文:https://blog.csdn.net/qj19842011/article/details/45675057
版权声明:本文为博主原创文章,转载请附上博文链接!
java中 try catch finally和return联合使用时,代码执行顺序的小细节的更多相关文章
- Java 中无返回值的方法在使用时应该注意的问题
Java 中的方法是形态多样的.无返回值的方法在使用时应该规避哪些问题呢? 一.不可以打印调用或是赋值调用,只能是单独调用(非常重要): 二.返回值没有,不代表参数就没有: 三.不能return一个具 ...
- Java中继承时静态块,构造块,构造函数的执行顺序
public class Father { static { System.out.println("Father静态块"); } { System.out.println(&qu ...
- 【转】Java中try catch finally语句中含有return语句的执行情况(总结版)
Java中try catch finally语句中含有return语句的执行情况(总结版) 有一点可以肯定,finally块中的内容会先于try中的return语句执行,如果finall语句块中也有r ...
- java中try{}catch{}和finally{}的执行顺序问题
今天我给大家讲解一下java的的错误和异常处理机制以及相关异常的执行顺序问题.如有不足的地方,欢迎批评指正~ 1.首相简单介绍一下java中的错误(Error)和异常(Exception) 错误和异 ...
- Java中try,catch,finally的用法
Java中try,catch,finally的用法,以前感觉还算熟悉,但看到一篇博文才有更深点的理解,总结网友博客如下. Java异常处理的组合方式: 1.try+catch 运行流程:运行到try ...
- java中break、continue、return作用
java中break.continue.return作用 0.首先要明确:break和continue是作用对象是循环体:而return的作用对象是方法 break:在执行完本次循环后,跳出所在的循环 ...
- 关于JAVA中事件分发和监听机制实现的代码实例-绝对原创实用
http://blog.csdn.net/5iasp/article/details/37054171 文章标题:关于JAVA中事件分发和监听机制实现的代码实例 文章地址: http://blog.c ...
- 当C#中带有return的TryCatch代码遇到Finally时代码执行顺序
编写的代码最怕出现的情况是运行中有错误出现,但是无法定位错误代码位置.综合<C#4.0图解教程>,总结如下: TryCatchFinally用到的最多的是TryCatch,Catch可以把 ...
- java中如何把图片转换成二进制流的代码
在学习期间,把开发过程经常用到的一些代码段做个备份,下边代码内容是关于java中如何把图片转换成二进制流的代码,应该能对各朋友也有用处. public byte[] SetImageToByteArr ...
随机推荐
- 2019 蓝桥杯省赛 A 组模拟赛(一)-修建公路
题目: 蒜头国有 nn 座城市,编号分别为 0,1,2,3,...,n-1.编号为 x 和 y 的两座城市之间如果要修高速公路,必须花费 x|y 个金币,其中|表示二进制按位或. 吝啬的国王想要花最少 ...
- swool配置ssl
1 yum install openssl --enable-openssl -y 2 切换在swoole 安装目录 cd /usr/local/swoole 3 ./configure --en ...
- Dalvik和ART
--摘自<Android进阶解密> DVM和ART都是在Zygote进程中诞生的 *DVM和JVM的区别* 1.基于的架构不同 DVM是基于寄存器的,它没有基于栈的虚拟机在复制数据时而使用 ...
- docker报错Service 'pwn_deploy_chroot' failed to build: Get https://registry-1.docker.io/v2/library/ubuntu/manifests/16.04:net/http: request canceled
这几天碰到师傅让我帮忙做pwn题环境,结果遇到了坑 第一种方法是:https://blog.csdn.net/zhaoyayua/article/details/60141660 解决办法是执行 vi ...
- 平时作业六 java
编写一个Java应用程序,使用Java的输入输出流技术将Input.txt的内容(Input.txt为文本文件)逐行读出,每读出一行就顺序为其添加行号(从1开始,逐行递增),并写入到另一个文本文件Ou ...
- Linux内核内存管理架构
内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...
- 大数据平台Hive数据迁移至阿里云ODPS平台流程与问题记录
一.背景介绍 最近几天,接到公司的一个将当前大数据平台数据全部迁移到阿里云ODPS平台上的任务.而申请的这个ODPS平台是属于政务内网的,因考虑到安全问题当前的大数据平台与阿里云ODPS的网络是不通的 ...
- Fio测试工具参数
以随机读为例:fio -ioengine=libaio -group_reporting -direct=1 -name=testsda -numjobs=1 --time_based --runti ...
- JAVA_AesCBC例子
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.Secre ...
- golang二进制bit位的常用操作
golang作为一热门的兼顾性能 效率的热门语言,相信很多人都知道,在编程语言排行榜上一直都是很亮眼,作为一门强类型语言,二进制位的操作肯定是避免不了的,数据的最小的单位也就是位,尤其是网络中封包.拆 ...