之前看过一篇关于 return 和 finally 执行顺序的文章,仅在 Java 的语言层面做了分析,这种分析很容易就会忘记 其实我倒觉得直接看 bytecode 可能来的更清晰一点。(耐心看完 才会有收获哦)
Try catch finally return 的爱恨情仇
先看一个只有 try-finally,没有 catch 的例子。
Try-finally
public class TryTest {
public void tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}
public void tryItOut() { }
public void wrapItUp() {}
}
字节码查看工具
这里使用的是idea 插件
tryFinally 编译后的字节码
0 aload_0
1 invokevirtual #2 <ExceptionTest.tryItOut> 方法
4 aload_0
5 invokevirtual #3 <ExceptionTest.wrapItUp> 方法
8 goto 18 (+10)
11 astore_1
12 aload_0
13 invokevirtual #3 <ExceptionTest.wrapItUp>
16 aload_1
17 athrow
18 return
如果没有抛出异常,那么它的执行顺序为
0 aload_0
1 invokevirtual #2 <ExceptionTest.tryItOut> 方法
4 aload_0
5 invokevirtual #3 <ExceptionTest.wrapItUp> 方法
18 return
如果出现 异常
Exception table:
from to target type
0 4 11 any
在0 ~ 4之间出现异常 (也就是 Try 中出现异常 就会直接跳转到 11 行执行)
11 astore_1
12 aload_0
13 invokevirtual #3 <ExceptionTest.wrapItUp>
16 aload_1
17 athrow
astore_1会把抛出的异常对象保存到local variable数组的第二个元素。下面两行指令用来调用成员方法wrapItUp。
12 aload_0
13 invokevirtual #3 <ExceptionTest.wrapItUp>
这两行去调用 finally中的方法 (12,13)
16 aload_1
17 athrow
调用成功 (16,17) 重新抛出异常
结论:
在try-finally中,try块中抛出的异常会首先保存在local variable中,然后执行finally块,执行完毕后重新抛出异常。
接下来看
try-return-finally 的执行顺序
public class TryTest {
public void tryFinally() {
try {
tryItOut();
return;
} finally {
wrapItUp();
}
}
// auxiliary methods
public void tryItOut() { }
public void wrapItUp() {}
}
字节码
0 aload_0
1 invokevirtual #2 <TryTest.tryItOut>
4 aload_0
5 invokevirtual #3 <TryTest.wrapItUp>
8 return
9 astore_1
10 aload_0
11 invokevirtual #3 <TryTest.wrapItUp>
14 aload_1
15 athro
从字节码中可以看出 return 虽然是在Try代码块中 但是经过编译之后 return是在 finally执行后后执行
如果try块中有return statement,一定是finally中的代码先执行,然后return。
接下来看 Try-catch-finally
public class TryTest {
public void tryFinally() {
try {
tryItOut();
} catch (Exception e) {
e.printStackTrace();
} finally {
wrapItUp();
}
}
// auxiliary methods
public void tryItOut() { }
public void wrapItUp() {}
}
字节码
0 aload_0
1 invokevirtual #2 <TryTest.tryItOut>
4 aload_0
5 invokevirtual #3 <TryTest.wrapItUp>
8 goto 30 (+22)
11 astore_1
12 aload_1
13 invokevirtual #5 <java/lang/Exception.printStackTrace>
16 aload_0
17 invokevirtual #3 <TryTest.wrapItUp>
20 goto 30 (+10)
23 astore_2
24 aload_0
25 invokevirtual #3 <TryTest.wrapItUp>
28 aload_2
29 athrow
30 return
出现异常的走向
这次 0~4出现异常 跳转 11 进入到 catch 捕获异常 之后再 跳转 23 行 执行finally 方法
Try-return-catch-finally 字节码
0 aload_0
1 invokevirtual #2 <TryTest.tryItOut>
4 aload_0
5 invokevirtual #3 <TryTest.wrapItUp>
8 return finally之后执行
9 astore_1
10 aload_1
11 invokevirtual #5 <java/lang/Exception.printStackTrace>
14 aload_0
15 invokevirtual #3 <TryTest.wrapItUp>
18 goto 28 (+10)
21 astore_2
22 aload_0
23 invokevirtual #3 <TryTest.wrapItUp>
26 aload_2
27 athrow
28 return
结论
在java的语言规范有讲到,如果在try语句里有return语句,finally语句还是会执行。它会在把控制权转移到该方法的调用者或者构造器前执行finally语句。也就是说,使用return语句把控制权转移给其他的方法前会执行finally语句。