从字节码的角度来看try-catch-finally和return的执行顺序
全篇以一个例子来说明:
先看如下的例子代码:
public class ExceptionTest {
public void testException(){
try{
inside_try();
}
catch(Exception e){
inside_catch(e);
}
finally{
inside_finally();
}
}
//分别为try块、catch块和finally块中被调用的函数
public void inside_try(){ }
public void inside_catch(Exception e){ }
public void inside_finally(){}
}
通过javap -c ExceptionTest
命令可以看到此类的字节码如下:
如果没有抛异常,那么它的执行顺序为:
0: aload_0
1: invokevirtual #2 // Method inside_try:()V
4: aload_0
5: invokevirtual #3 // Method inside_finally:()V
8: goto 31
31: return
即先执行try里面的代码块,然后执行finally里面的代码块。
如果try中抛了一个异常,那么JVM会在如下的异常表中寻找跳转位置。
Exception table:
from to target type
0 4 11 Class java/lang/Exception
0 4 24 any
11 17 24 any
从异常表中,可以看到有三种异常情况发生导致执行的路径不同:
第一种:如果位于0到4字节之间的命令(即try块中的代码)抛出了Class java/lang/Exception
类型的异常,则跳转到第11个字节开始执行catch中的代码;
第二种:如果位于0到4字节之间的命令(即try块中的代码)抛出了任何类型的异常,则跳转到第24个字节开始执行finally里面的代码。
第三种:如果位于11到17字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第24个字节开始执行finally里面的代码。
先看第一种情况:如果位于0到4字节之间的命令(即try块中的代码)抛出了Class java/lang/Exception
类型的异常,则跳转到第11个字节开始执行catch中的代码
指令如下:
11: astore_1
12: aload_0
13: aload_1
14: invokevirtual #5 // Method inside_catch:(Ljava/lang/E
xception;)V
17: aload_0
18: invokevirtual #3 // Method inside_finally:()V
21: goto 31
31: return
astore_1会把抛出的异常对象保存到local variable数组的第二个元素。剩余的几行指令用来调用catch和finally块中的方法。
再看第二种情况:如果位于0到4字节之间的命令(即try块中的代码)抛出了任何类型的异常,则跳转到第24个字节开始执行finally中的代码
指令如下:
24: astore_2
25: aload_0
26: invokevirtual #3 // Method inside_finally:()V
29: aload_2
30: athrow
31: return
astore_2会把抛出的异常对象保存到local variable数组的第二个元素。下面的两行指令用来调用finally块中的方法。
25: aload_0
26: invokevirtual #3 // Method inside_finally:()V
最后通过如下的指令抛出异常
29: aload_2
30: athrow
最后一种情况,如果位于11到17字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第24个字节开始执行finally里面的代码。
这种情况的代码与上面一样,直接执行finally块中的代码。
再来看下try和catch中有return语句的情形
例子代码如下,在catch块中有return语句:
public class ExceptionTest {
public void testException(){
try{
inside_try();
}
catch(Exception e){
inside_catch(e);
return;//catch块中有return语句
}
finally{
inside_finally();
}
}
//分别为try块、catch块和finally块中被调用的函数
public void inside_try(){ }
public void inside_catch(Exception e){ }
public void inside_finally(){}
}
用javap -c ExceptionTest命令查看字节码如下:
从字节码可以看出,即使try块中发生了异常,catch块中的return语句也是在finally块后面执行。