finally语句一定会执行吗,很多人认为一定会,其实未必,只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行。假如在try语句之前执行了return操作或者抛出异常,那么try语句不会执行,finally也不会执行。
那try语句得到执行,finally块就一定会执行吗?
其实也未必,如果在try语句中出现System.exit(0)方法,终止了 Java 虚拟机的运行,那么finally语句也一样不会被执行,当然这种属于比较特殊的情况,通常不会出现。
那没有上面这两种情况,finally就一定会执行吗?
不是。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。
我们可以看官方网站上的《The Java Tutorials》找到一段关于finally的描述:
*******************************************************************************
The finally Block
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return,continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.
Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.
*******************************************************************************
翻译一下,这段文字说的是:
finally块
finally块总是在try块退出时执行。 这确保即使发生意外异常也会执行finally块。 但是finally有助于不仅仅是异常处理 - 它允许程序员避免因为一个返回,继续或中断而错过清除代码。 将清理代码放在finally块中始终是一个很好的做法,即使没有预期到异常。
注意:如果JVM在执行try或catch代码时退出,那么finally块可能不会执行。 同样地,如果执行try或catch代码的线程被中断或者被杀死,即使应用程序作为一个整体继续,finally块可能不会执行。
接下来,我们看一下 finally 语句块是怎样执行的。在排除了以上 finally 语句块不执行的情况后,finally 语句块就得保证要执行,既然 finally 语句块一定要执行,那么它和 try 语句块与 catch 语句块的执行顺序又是怎样的呢?还有,如果 try 语句块中有 return 语句,那么 finally 语句块是在 return 之前执行,还是在 return 之后执行呢?
我们来看一道题目,这是之前在cvte的2015面试题中看到的。
5. 下面程序的输出是什么?
public class FinallyTest1 {
static char label;
public static void main(String[] args) {
System.out.println(test1());
System.out.println(label);
}
public static char test1() {
try {
System.out.println('A');
return label = 'A';
}
finally {
System.out.println('B');
label = 'B';
}
}
}
额。。.确实不会。答案是
A
B
A
B
对于这个,之前确实没有什么了解,于是我自己运行了一下
package k; public class k { static char label; public static void main(String[] args) { System.out.println(test1()); System.out.println('3'); System.out.println(label); } public static char test1() { try { System.out.println('A'); System.out.println('1'); return label='A'; } finally { System.out.println('B'); System.out.println('2'); label = 'B'; } } }
结果输出:
A
1
B
2
A
3
B
其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。不然finally就不会执行了。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。
那为什么在return的时候已经把label赋值为A了,怎么最后还是B呢?
关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。
最后给大家看几个例子:
1、
public class Test { public static void main(String[] args) { System.out.println("return value of getValue(): " + getValue()); } @SuppressWarnings("finally") public static int getValue() { int i = 1; try { i = 4; } finally { i++; return i; } } }
结果:return value of getValue(): 5
2、
public class Test { public static void main(String[] args) { System.out.println("return value of getValue(): " + getValue()); } public static int getValue() { int i = 1; try { i = 4; } finally { i++; } return i; } }
结果:return value of getValue(): 5
3、
public class Test { public static void main(String[] args) { System.out.println(test()); } public static String test() { try { System.out.println("try block"); return test1(); } finally { System.out.println("finally block"); } } public static String test1() { System.out.println("return statement"); return "after return"; } }
结果:
try block
return statement
finally block
after return