finally关键字通常和try/catch搭配使用,当程序执行到try中时,finally中的代码一定会执行。
finally为什么会一定执行?
public static void main(String[] args) {
try {
System.out.println("try");
} catch (Exception e){
System.out.println("catch");
} finally {
System.out.println("finally");
}
}
将上面程序运行后反编译得到字节码指令如下(摘取了一部分,先扫一遍,耐心看完会有收获的^_^):
stack=2, locals=3, args_size=1 ①
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String try
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #5 // String finally
13: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: goto 50
19: astore_1 ②
20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #7 // String catch
25: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #5 // String finally
33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: goto 50
39: astore_2 ③
40: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
43: ldc #5 // String finally
45: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
48: aload_2
49: athrow
50: return
Exception table: ④
from to target type
0 8 19 Class java/lang/Exception
0 8 39 any
19 28 39 any
Exception table的解释:④中Exception table就是异常表,当[from,to)中出现type类型的异常时就跳转到target继续执行。例如在[0,8)中发生了Exception的异常就跳转到19(astore_1)继续执行。至于这个异常表为什么还有后两个?且埋下一个伏笔。
在①中执行了try和finally的代码,②中执行了catch和finally的代码,在try和catch后面都执行了finally的代码,这是finally初步实现的方式,保证try和catch执行完后一定会执行finally。
在③中,return操作前finally代码又被执行了,这是为什么?
在这个问题之前有两个问题需要思考:
1:如果catch捕获的是空指针异常,而try中出现了by zero异常,由于type不一样,catch捕获不到,此时程序会由于异常而终止,finally会不会执行不了?不妨运行一下:
public static void main(String[] args) {
try {
System.out.println("try");
throw new ArithmeticException();
} catch (NullPointerException e){
System.out.println("catch");
} finally {
System.out.println("finally");
}
}
这样的运行情况说明了异常表中的第二项存在的原因:finally会捕获在try中catch捕获不到的异常,执行完finally的代码然后将这个异常抛出。
2:如果catch中出现异常finally还会执行吗?再运行一下:
public static void main(String[] args) {
try {
System.out.println("try");
throw new NullPointerException();
} catch (NullPointerException e){
System.out.println("catch");
throw new ArithmeticException();
} finally {
System.out.println("finally");
}
}
这进一步说明了异常表中的第三项存在的原因:finally会捕获在catch代码中的异常,执行完finally的代码然后将这个异常抛出。
此时看③中finally代码的执行是不是有了新感悟?当捕获到try中catch捕获不到的或catch中的异常时,将异常存储在slot 2中(astore_2:将操作数栈栈顶元素存储在局部变量表的slot 2中并在栈中弹出此元素),然后执行finally代码,最后加载异常并抛出后返回。
finally捕获try中catch捕获不到的或catch中的异常又给它的"一定执行"上了一层保险。
注意:在多个catch或multi-catch的情况下,finally代码在每个catch后都执行,异常表也会加上相应的项。
当try中出现return时,finally会执行吗?
public static int test2(){
try {
return 10;
} finally {
System.out.println("finally");
}
}
public static void main(String[] args) {
System.out.println(test2());
}
如图:finally依然会执行,继续看看它的字节码指令:
stack=2, locals=2, args_size=0 ①
0: bipush 10
2: istore_0
3: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
6: ldc #5 // String finally
8: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
11: iload_0
12: ireturn
13: astore_1 ②
14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #5 // String finally
19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: aload_1
23: athrow
Exception table:
from to target type
0 3 13 any
①中是try的代码,首先bipush 10将return的10压入操作数栈,然后将其弹出存放在局部变量表的一号单元,随后执行finally代码,在最后加载存放在局部变量表的一号单元到操作数栈,然后return,保证了finally的执行。
所以在try中出现return,如果有finally关键字,它需要先将返回信息暂存一下,等到finally代码执行完后再进行返回,这给finally的"一定执行"加了一层保险。
总结一下:
①:try和catch代码后面都执行了finally的代码;
②:finally可以捕获并抛出在try中catch捕获不到的或catch中的异常;
③:在try中出现return,如果有finally关键字,它需要先将返回信息暂存一下,等到finally代码执行完后再进行返回。