本文讲解过于繁琐,已重新修改至 关于 Java finally 执行顺序 -- 修改版。
首先来看看这道题:
public class test1 {
static String s = "A";
public static void main(String[] args) {
System.out.println(test());
System.out.println(s);
}
private static String test() {
try {
System.out.println("A");
return s = "A";
} finally {
System.out.println("B");
s = "B";
}
}
}
执行结果是 ABAB
,刚看到这个结果,我是比较诧异的,利用 IDEA 的单步调试也没弄明白,来看看我当时的思路:
try
中的输出语句,输出 "A"
try
下还有的 finally
语句,所以执行 finally
中的输出语句,输出 "B"
"B"
赋值给变量 s
try
中的 return
语句,再次将 "A"
赋值给 s
,将 "A"
最为返回值,返回 main
方法中。(此时 s = "A"
)main
方法中的第一行输出语句,输出 "A"
,然后执行 main
方法中的第二条输出语句,输出 "A"
所以,结果不应该是 “ABAA” 么,因为 return 中已经将 “A” 赋值给 s 了,那么 s 的值不应该是 “A” 么,这时返回值就是 “A”,再怎么第二条输出语句也不能是 “B” 呀,可看着开发工具输出的结果,我也很无奈,我当时是这个表情。。。
但我相信开发工具是不会骗我的,一定是我自己的理解有问题,然后自己又写了几个类似的例子来测试,最后得出的结论是: finally 语句块在 return 语句之后,return 返回之前执行,可能这句话不是那么容易理解,来看下面的例子吧,看完我相信你应该能明白我这句话是什么意思了。
证明:finally 语句块在 return 语句之后,return 返回之前执行。
public class FinallyTest1 {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int b = 20;
try {
System.out.println("try block");
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
}
return b;
}
}
运行结果:
try block
finally block
b>25, b = 100
100
可以根据运行结果得知,在 finally
语句块执行之前,return b += 80;
中的 b + =80;
这部分已经运行,但是没有返回,然后等 finally
执行完以后才返回结果的。
如果觉得这个 例 1
还不足以说明这个情况的话,下面再加个例子加强证明结论:
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("return statement");
return "after return";
}
}
运行结果:
try block
return statement
finally block
after return
这下总明白了吧,执行 finally
之前将 return
后的 test12()
方法先执行了,然后再执行的 finally
语句。
前面两个例子证明了原题得出的结论,但是这个结论好像还不能足矣证明原题中的结果为什么是"ABAB"
,还有一个新问题:
如果,finally 中对 return 返回的变量进行了修改,那么 return 返回的是修改前的值还是修改后的值?
我们在 例 1
的 finally
语句块中加上一句 b = 10;
,那么结果又会是什么呢?
public class FinallyTest2 {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int b = 20;
try {
System.out.println("try block");
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b = 10;
}
return b;
}
}
运行结果:
try block
finally block
b>25, b = 100
100
可以看到 return
的值并没有被 finally
中的语句改变,其实如果这样得出结论:finally 中对 return 返回的变量进行了修改,并不会影响 try 中 return 的值,是不负责任的,因为我们只考虑了基本数据类型,如果是引用数据类型,还会是这种结果么?
接 拓展问题 1
的又一个问题,对于引用数据类型的情况:
public class FinallyTest3 {
public static void main(String[] args) {
System.out.println(test1().getName());
}
public static Student test1() {
Student stu = new Student();
try {
stu.setName("Try");
return stu;
} catch (Exception e) {
stu.setName("Catch");
} finally {
stu.setName("Finally");
}
return stu;
}
}
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果:
Finally
可以看到,对于基本数据类型和引用数据类型,结论是不同的,再次总结下结论吧:
finally
语句块中对 try
语句块的 return
的变量进行了修改,return
的则是修改前的值。finally
语句块中对 try
语句块的 return
的变量进行了修改,return
的则是修改后的值。看到这里,不知道你有没有想到这个结论与 Java 的方法调用时的所谓的 值传递
和 引用传递
的结论有些类似。
既然得出了这个结论,那么这个结论是必然的情况么?一定正确么?别急,来看下一个例子。
对于 拓展问题 2
我再做一个增强版的,其实就是在第 17 行之下添加一条语句 stu = null
:
public class FinallyTest4 {
public static void main(String[] args) {
System.out.println(test1().getName());
}
public static Student test1() {
Student stu = new Student();
try {
stu.setName("Try");
return stu;
} catch (Exception e) {
stu.setName("Catch");
} finally {
stu.setName("Finally");
stu = null;
}
return stu;
}
}
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果:
Finally
这时,你可能都有骂人的冲动了,What ?咋回事,咋什么结论都不对呢,不是说好的引用数据类型会改变 return
的值么,这里都将 stu
修改为 null
了,怎么还能 return
了 "Finally"
呢?
别激动,别激动,不卖关子了,我直接说了:
先说基本数据类型,由于基本数据类型是 值传递 ,所以在
try
里的return
执行的时候,就先将变量的值隐性的作为最终返回的值。 同样的,对于引用数据类型,只是将该变量所指向的内存地址隐性的作为最终返回的值,所以即使将stu = null
,也无所谓了,因为早在执行finally
之前,try
里的return
就已经拿到了stu
所指向的地址。 这里多说一句,其实 Java 里都是值传递,只不过基本数据类型的值是真正的值,而引用数据类型是地址值而已。(如果你看这句话更晕的话,就先跳过去这句话,反正本篇文章也不是为了解释这个的。)
问:如果 try
与 finally
中都有 return
语句,那么到底会返回哪一个呢?
会返回
finally
中的return
。
问:如果 try
里有 return
语句,整个 try-catch-finally
块之外也有一个 return
语句,那么哪个会执行,一定是这样么?
在
try
里没有发生异常的情况下,是try
里的return
会执行,但发生了异常,则反之。
问:如果 catch
中有 return
语句呢?当然只有在异常的情况下才有可能会执行,那么是在 finally
之前就返回吗?
当发生异常后,
catch
中的return
执行情况与未发生异常时try
中return
的执行情况完全一样。
finally 块的语句在 try 或 catch 中的 return 语句执行之后返回之前执行,且 finally 里的修改语句可能影响也可能不影响 try 或 catch 中 return 已经确定的返回值,若 finally 里也有 return 语句则覆盖 try 或 catch 中的 return 语句直接返回。