[例子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/