深入剖析java的try…catch…finally语句
一、前言
前些天参加面试的时候有一道题:
public class test {
public static void main(String[] args){
try {
return;
}
finally{
System.out.println("finally...");
}
}
}
以上程序的执行结果是什么?
当时觉得finally块肯定会被执行到的,而这段程序在try块里就已经返回了,所以选了“编译出现错误”这个选项,回来之后验证了一下,结果是输出“finally…”,越发觉得这个问题很有趣。
二、剖析
1. 从字节码分析
为了更好的说明问题,选用一篇博客里的例子[1]进行说明,源码:
public class Test {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(Test.test());
}
}
按照一般的思路,首先程序执行try语句块,把变量t赋值为try,由于没有发现异常,接下来执行finally语句块,把变量t赋值为finally,然后return t,则t的值是finally,最后t的值就是finally,程序结果应该显示finally,但是实际结果为try。为什么会这样,我们不妨先看看这段代码编译出来的class对应的字节码,看虚拟机内部是如何执行的。
我们用javap -verbose Test 来显示目标文件(.class文件)字节码信息。
系统运行环境:win7 64位
jdk信息:java version "1.8.0_05",Java(TM) SE Runtime Environment (build 1.8.0_05-b13),Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
编译出来的字节码部分信息,我们只看test方法,其他的先忽略掉:
public static final java.lang.String test();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=1, locals=4, args_size=0
0: ldc #2 // String
2: astore_0
3: ldc #3 // String try
5: astore_0
6: aload_0
7: astore_1
8: ldc #4 // String finally
10: astore_0
11: aload_1
12: areturn
13: astore_1
14: ldc #6 // String catch
16: astore_0
17: aload_0
18: astore_2
19: ldc #4 // String finally
21: astore_0
22: aload_2
23: areturn
24: astore_3
25: ldc #4 // String finally
27: astore_0
28: aload_3
29: athrow
Exception table:
from to target type
3 8 13 Class java/lang/Exception
3 8 24 any
13 19 24 any
LineNumberTable:
line 5: 0
line 8: 3
line 9: 6
line 14: 8
line 10: 13
line 11: 14
line 12: 17
line 14: 19
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
观察Code部分:
第[0-2]行,给第0个变量赋值“”,也就是String t="";
第[3-5]行,也就是执行try语句块 赋值语句 ,也就是 t = "try";
第[6-7]行,重点是第7行,把第t对应的值"try"赋给第1个变量,但是这里面第1个变量并没有定义这个比较奇怪;
第[8-10] 行,对第0个变量进行赋值操作,也就是t="finally";
第[11-12]行,把第1个变量对应的值返回;
通过字节码,我们知道,在return之前,虚拟机会创建一个中间变量,我们暂时可以称为t’,然后把t的值赋值给t’,接下来去执行finally块的内容,最后返回t’,所以即使在finally块里修改了t,但是return返回的是t’,所以最后输出的是t’的内容,如下图所示:

2. 如果try和finally块里面修改的是可变的对象
class myObject{
private int value = 0;
public myObject(int value){
this.value = value;
}
public void setValue(int value){
this.value = value;
}
public void print(){
System.out.println("obj:" + this + ",value:" + value);
}
}
public class Test {
@SuppressWarnings("finally")
public static final myObject test() {
myObject myObj = null;
try {
myObj = new myObject(1);
System.out.println("in try block");
myObj.print();
return myObj;
} catch (Exception e) {
myObj.setValue(2);
return myObj;
} finally {
myObj.setValue(3);
}
}
public static void main(String[] args) {
myObject newObj = test();
System.out.println("after return");
newObj.print();
}
}
输出:
in try block
obj:myObject@15db9742,value:1
after return
obj:myObject@15db9742,value:3
在这个例子中,即使myObj会赋值给myObj’,然而他们都指向同一个对象,因此在finally块中对这个对象的修改当然反映到myObj’中。
3. try和finally块中都有return语句
还是用String的例子,不过finally语句增加了return语句
public class Test {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
} public static void main(String[] args) {
System.out.print(Test.test());
}
}
最后输出:
finally
可见这种情况,执行完finally之后就直接返回了。
4. catch语句被执行的情况并且有return语句
public class Test {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(Test.test());
}
}
最后输出:
catch
可见try和catch语句里都存在使用中间变量的情况。
5. catch块中抛出异常的情况
public class Test {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
Integer.parseInt(null);
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(Test.test());
}
}
输出:
Exception in thread "main" java.lang.NumberFormatException: null
at java.lang.Integer.parseInt(Integer.java:542)
at java.lang.Integer.parseInt(Integer.java:615)
at Test.test(Test.java:12)
at Test.main(Test.java:20)
执行过程大概是,执行try块,Integer.parseInt(null)语句抛出异常,进入catch语句,然后又抛出异常,然后执行finally块,对t进行赋值,因为finally没有返回,所以执行完之后,catch把异常抛出。
如果finally块中有return语句呢,在finally块最后加多一条语句”return t;”,最后输出:
finally
6. finally块中抛出异常
public class Test {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
String.valueOf(null);
return t;
}
}
public static void main(String[] args) {
System.out.print(Test.test());
}
}
输出:
Exception in thread "main" java.lang.NullPointerException[Finished in 1.1s with exit code 1]
at java.lang.String.<init>(String.java:166)
at java.lang.String.valueOf(String.java:2993)
at Test.test(Test.java:14)
at Test.main(Test.java:20)
可见执行到finally块,产生异常之后会终止当前的其他操作,向上抛出异常。
三、小结
- try,catch,finally语句中,如果在try/catch块中存在return语句,finally块没有return语句,try/catch块会产生一个临时变量(t’)存储return 语句中的变量(t),如果这个变量类型是值类型或者不可变对象,则在finally块中对变量t的修改不会影响到try/catch中返回的结果;如果是可变对象类型,则结果会影响;
- 如果finally块中有return语句,则try和catch中的return语句都会忽略;
- 如果finally块中抛出异常,则停止try…catch…finally中的其他操作,直接向上抛出异常。
四、参考
1. java中关于try、catch、finally中的细节分析
深入剖析java的try…catch…finally语句的更多相关文章
- 【转】Java中try catch finally语句中含有return语句的执行情况(总结版)
Java中try catch finally语句中含有return语句的执行情况(总结版) 有一点可以肯定,finally块中的内容会先于try中的return语句执行,如果finall语句块中也有r ...
- Java中try catch finally语句中含return语句的执行情况总结-编程陷阱
前言:有java编程基础的人对java的异常处理机制都会有一定了解,而且可能感觉使用起来也比较简单,但如果在try catch finally语句块中遇到return语句,开发者可能就会遇到一些逻辑问 ...
- Java中try catch finally语句中含有return语句的执行情况(总结版)
在这里看到了try >但有一点是可以肯定的,finally块中的内容会先于try中的return语句执行,如果finall语句块中也有return语句的话,那么直接从finally中返回了,这也 ...
- Java中try catch finally语句中含有return语句的执行情况
finally块中的内容会先于try中的return语句执行,如果finall语句块中也有return语句的话,那么直接从finally中返回了,这也是不建议在finally中return的原因.下面 ...
- Java Iterator, ListIterator 和 foreach语句使用
Java Iterator, ListIterator 和 foreach语句使用 foreach语句结构: for(part1:part2){part3}; part2 中是一个数组对象,或者是带 ...
- 深入剖析Java编程中的中文问题及建议最优解决方法
摘录自:http://fafeng.blogbus.com/logs/3062998.html http://www.blogbus.com/fafeng-logs/3063006.html 深入剖析 ...
- Java中try catch finally的执行顺序问题
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的.更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 ...
- Java中try,catch,finally的用法
Java中try,catch,finally的用法,以前感觉还算熟悉,但看到一篇博文才有更深点的理解,总结网友博客如下. Java异常处理的组合方式: 1.try+catch 运行流程:运行到try ...
- 深度剖析java中JDK动态代理机制
https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...
随机推荐
- Jenkins配置slave遇到“无法启动该应用程序”的问题
飞测说:最近在负责持续集成相关的工作,我们用的是jenkins+svn+maven+sonar, 今天在用slave这块出现了一个问题,排查了好久才解决,踩过的坑,现在和大家一起看看,希望对大家有帮助 ...
- Servlet不是线程安全的。
要解释为什么Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的. 当Tomcat接收到Client的HTTP请求时,Tomcat从线程池中取出一个 ...
- tensorflow中summary操作
tf中 tensorboard 工具通过读取在网络训练过程中保存到本地的日志文件实现数据可视化,日志数据保存主要用到 tf.summary 中的方法. tf.summary中summary是tf中的一 ...
- 20155226 2016-2017-2 《Java程序设计》第8周学习总结
20155226 2016-2017-2 <Java程序设计>第8周学习总结 教材学习内容总结 通用API 日志 java.util.logging包提供了日志功能相关类与接口,使用日志的 ...
- Java编程之Map中分拣思想。
题目:给定一个字符串,求出字符串中每一个单词在字符串中出现的次数 旨意:map的分拣思想. 每一个key的包装类,存放出现的次数 /** * 作为包装类,用来存放英文单词,和该英文单词出现的次数 * ...
- python 获取excel文件内sheet名称列表
xl = pd.ExcelFile('foo.xls') xl.sheet_names # see all sheet names xl.parse(sheet_name) # read a spec ...
- 设计模式(Python)-单例模式
本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题: 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的 ...
- Telnet远程重启路由器TP-LINK
突然断网,以前房东的路由器管理页面可以打开,今天突然间就打不开了.ping了下,可以ping通,于是就想起了房东的路由器是TP-LINK的 可以 telnet登陆的.每次,断网,我都会重启房东的路由器 ...
- VsCode中运行nodeJs代码的简单方法
VsCode安装包默认内置的node debug插件需要配置工程调试运行文件才能正常运行,对于想要运行一个简单的js文件或者就是一段js代码时比较麻烦,为此可以安装Code Runner插件 安装完后 ...
- vue2.0实现一个模态弹框,内容自定义(使用slot)
定义模态框:合理使用插槽 model.vue <!-- 模态弹窗 --> <template> <div class="self-modal" v-s ...