[例子 1] final 的意义
public static int test1(){
int i = 0;
try{
System.out.println("try"); //1
return i;
}finally {
System.out.println("finally"); //2
}
}
执行结果
try
finally
分析
无论 try 语句或 catch 语句中是否有 return 语句,finally 语句都一定会被执行。
总结
总结来说,我们可以把必须要执行的操作(比如资源释放操作:关闭流、关闭数据库连接,释放锁)放到 finally 语句里,以确保其一定会被执行。
- 在某些情况下,try 语句压根就没有执行到,那么 finally 语句也一定就不会执行到了。
- 还有一种情况,就是在 try 块中有
System.exit(0);
这样的语句,System.exit(0);
会终止 Java 虚拟机(JVM)。因此,连 JVM 都停止了,当然 finally 语句也不会被执行到。
[例子 2]
我们再来看一个程序:
public class Main {
public static void main(String[] args) {
System.out.println(test4());
}
public static int test4() {
int b = 20;
try {
System.out.println("try block");
b = b / 0;
return 1;
} catch (Exception e) {
b += 15;
System.out.println("catch block");
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
return 204;
}
}
结果
try block
catch block
finally block
b>25, b = 35
204
说明
打印 204
说明,当在执行 try 语句块的过程中抛出异常,则会直接进入 catch 语句(前提当然是存在 catch 语句),而对于在 try 语句块中的抛出异常位置后的代码,不会被执行。
[例子 3]
再来看一个程序:
public class FinallyTest1 {
public static void main(String[] args) {
System.out.println(test11());
}
public static String test11() {
try {
System.out.println("try block");
return test12();
} finally {
System.out.println("finally block");
}
}
public static String test12() {
System.out.println("test12");
return "test12 return";
}
}
结果
try block
test12
finally block
test12 return
说明
return statement
先于 finally block
被打印,这说明:
System.out.println("try block");
先被执行- 开始执行
return test12();
中的test12()
:- 执行
System.out.println("return statement");
- 执行
test12()
的return "test12 return"
,即函数test11
的返回值被赋值为test12 return
;
- 执行
- 执行 finally 语句中的
System.out.println("finally block");
test11()
函数返回,返回值为test12 return
;- 打印
test11()
函数的返回值,即打印test12 return
。
结论
try 语句块或 catch 语句块中的 return 语句会先被执行(但不会立即返回),之后再去执行 finally 语句块。
而当等到 finally 语句块执行完成后,函数返回才会执行。
[例子 4] - 值类型
再来看一个程序:
public class Main {
public static void main(String[] args) {
int j = query();
System.out.println(j);
}
public static int query() {
int i = 0;
try {
System.out.print("try\n");
return i += 40;
} catch (Exception e) {
System.out.print("catch\n");
i += 20;
} finally {
System.out.print("finally1 i:"+i + "\n");
i += 10;
System.out.print("finally2 i:"+i + "\n");
System.out.print("finally3\n");
//return i;
}
System.out.print("finish");
return 200;
}
}
执行结果
try
finally1 i:40
finally2 i:50
finally3
40
分析
- 输出
finally1 i:40
,说明在执行 final 语句块之前,i += 40
会被执行。 - 输出
finally1 i:40
,同时说明即使 try 语句块中有 return 语句块,fianlly 还是会被执行。这就是所谓的”finally 一定会执行 “。 - 输出
finally2 i:50 finally3 40
,说明当执行 try 语句中的return i += 40;
时,JVM 会先执行i += 40
(等价于i = i + 40
),因此此时 i 的值为 40,并且将变量 i 复制一份(暂且称为 i_return)。此后,JVM 继续去执行 finally 语句(注意,finally 语句中没有 return 语句)。 finish
没有被输出,说明只要在 try 语句块和其对应的 finally 语句块中包含 return 语句,则在整个 try 结构后的代码永远不会被执行。
结论
- finally 语句是在 return 语句执行后,return 语句返回之前执行的。
- 从 JVM 管理内存的角度来分析,return 返回后,就代表着方法执行结束,相应的该方法的栈帧就出栈了。而这个时候也就意味着,return 返回必须在最后执行(否则会出现该方法对应的栈帧已经出栈,但是该方法(的 finally 语句块)任然在执行),所以 finally 语句是在 return 返回之前执行的!
- 当在 try 语句块或 catch 语句块中包含 return 语句(而 finally 语句块中不包含 return 语句),且 return 语句中包含赋值操作(或修改值操作),且赋值或修改的变量是值类型时:
- return 语句中的赋值先被执行
- 赋值后的新变量会被复制一份
- 执行 finally 语句块。因此,在 fianlly 语句块中修改 try 语句块或 catch 语句块中 return 语句中的变量不会影响 return 的结果
[例子 5] - 值类型
再来看一个程序:
public class Main {
public static void main(String[] args) {
int j = query();
System.out.println(j);
}
public static int query() {
int i = 0;
try {
System.out.print("try\n");
return i += 40;
} catch (Exception e) {
System.out.print("catch\n");
i += 20;
} finally {
System.out.print("finally1 i:"+i + "\n");
i += 10;
System.out.print("finally2 i:"+i + "\n");
System.out.print("finally3\n");
return i;
}
System.out.print("finish");
return 200;
}
}
执行结果
try
finally1 i:40
finally2 i:50
finally3
50
分析
注意!这个例子上上面一个例子唯一的区别在于,在 finally 语句块中存在一个 return 语句。
输出 50
,说明:
- 当执行 try 语句块中的
return i += 40;
时,JVM 会先执行i += 40
(等价于i = i + 40
),因此执行后 i 的值为 40,并且将变量 i 复制一份(暂且称为 i_return1)。此后,JVM 继续去执行 finally 语句块。 - 执行 finally 语句块中的
i += 10;
(此后,i 的值为 50)。 - 执行 finally 语句块中的
return i;
,此时,会将变量 i 复制一份(称为 i_return2),并且将变量 i_return2 的值作为函数的返回值返回。
总结
当在 try 语句块或 catch 语句块中包含 return 语句,且 finally 语句块中也包含 return 语句),且 return 语句中包含赋值操作(或修改值操作),且赋值或修改的变量是值类型时:
- try 语句块中的 return 语句中的赋值先被执行
- 赋值后的新变量会被复制一份
- 执行 finally 语句块,
- finally 语句块中的 return 语句被执行
- 将 finally 语句块中的 return 语句中的值作为函数的返回值(而 try 语句块中的 return 语句相当于被覆盖了)
[例子 6] - 引用类型
如果这个返回值是引用类型,情况又会有所不同。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> cats = new ArrayList<>();
cats = query(cats);
System.out.println("----");
for(String cat : cats)
System.out.println(cat);
}
public static List<String> query(List<String> cats) {
int i = 0;
try {
System.out.print("try\n");
cats.add("xiaoMeng");
return cats;
} catch (Exception e) {
System.out.print("catch\n");
} finally {
System.out.print("finally\n");
cats.add("qiaoGeLi");
}
System.out.println("finish");
return null;
}
}
执行结果
try
finally
----
xiaoMeng
qiaoGeLi
分析
由于 cats 是 List 类型,即一个引用类型。对于引用类型的复制只能在当前进程栈帧的局部变量表中复制一份,而其指向到位于堆(Heap)中的内存地址并没有改变(本质上,仍然是同一个对象)。
结论
当在 try 语句块或 catch 语句块中包含 return 语句,且 finally 语句块中也包含 return 语句),且 return 语句中包含赋值操作(或修改值操作),且赋值或修改的变量是引用类型时,无论 finally 语句块中有没有 return 语句,若在 finally 语句块中修改这个引用对象,都会影响最终函数返回值的结果。
总结
- finally 语句总是在 retrun 语句执行后,return 返回之前执行的,也就是说 finally 必执行(当然是建立在 try 执行的基础上)
- 当在 try 语句或 catch 语句中有 return 语句时,finally 语句中修改了返回值的值。若修改的返回值为基本数据类型(比如 int,double 之类),finally 语句中没有 return 语句,则 finally 语句中对返回值的修改不会影响最终的返回结果
- 当在 try 语句或 catch 语句中有 return 语句时,finally 语句中修改了返回值的值。若改返回值为引用类型(比如对象,list,map 之类),无论 finally 语句中有没有 return 语句,finally 语句中对返回值的修改都会影响最终的返回结果
Reference
- finally 到底是在 return 之前执行还是 return 之后执行? - https://mp.weixin.qq.com/s/--wUdzWCKH_cV7SQR_CC9A
- Java finally 语句到底是在 return 之前还是之后执行? - https://www.cnblogs.com/lanxuezaipiao/p/3440471.html
- Does a finally block always get executed in Java? - https://stackoverflow.com/questions/65035/does-a-finally-block-always-get-executed-in-java
- 关于 Java 中 finally 语句块的深度辨析 - https://www.ibm.com/developerworks/cn/java/j-lo-finally/