写在开头
这个问题真的困扰了我很久,感觉简直像一个哲学(?)问题。
私下和朋友们对这个问题讨论了很久,又在网上查找了很多相关资料,终于还是把这个问题理清楚了。(自认为 )
我的结论是:在try…catch语句中,当程序执行完return后的表达式后,会转而执行finally语句块,最后再继续执行return。
…这个答案看起来是不是还是很哲学?下面我来给出详细解释。
return和finally的定义
首先来看一下 return 和 finally 的定义:
- return:方法的结束标志,它导致该方法退出,并返回return后的那个值(在返回类型为void的方法里面,也有个被省略的return)。
- finally:作为异常处理的一部分,它只能用在try…catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。
return作为方法的“结束标志”,也就是说执行完return语句后,方法也就结束了。那么return看起来就是那个最后才执行的语句。
finally表示后面的语句块“最终一定会被执行”,细细品味,似乎只是在说明这段语句块肯定会执行的,但是不是最后才执行的就不一定了。
下面我们用代码来试验一下。
试验代码
定义如下方法,执行并输出
public static int test() {
int result = 0;
try {
throw new Exception();
}catch (Exception e) {
return ++result;
}finally {
result += 2;
}
}
public static void main(String[] args) {
System.out.println("result = " + test());
}
输出的结果为 result = 1。
这个结果说明 finally 语句块中的内容并没有奏效。通过debug模式来观察这一过程,结果如下:
-
在 throw new Exception() 处打上断点后,可以看到方法进入了 catch 语句块中,并开始执行 return 语句。此时 result = 0,return 后的表达式还没有执行。
-
Step Over后,可以看到 result 的值变为 1(执行了 ++result),并开始执行 finally 语句块
-
继续Step Over,result 的值变为 3(执行了 result += 2),方法又跳回了 return 语句
-
接着,test() 方法执行完毕,可以看到此时方法的返回值为 1
-
最后,控制台输出 “result = 1”。
结论
由此可见,执行的顺序是:return 后的表达式(++result) → finally 语句块(result += 2)→ return 返回值
但是为什么 finally 语句块并没有奏效呢?
这是因为当代码执行完 return 语句后的表达式(++result)后,会将要返回的值(result = 1)先存入一个局部变量表(假设这个变量名为 returnedValue,即 returnedValue = 1)。然后再执行 finally 代码块(result += 2),这个代码块修改的是 result 的值而不是 returnedValue 。上面代码中 result 作为返回值,但 result 变量本身与返回值 returnedValue 存放在不同的位置,所以修改了 result 后,returnedValue 并未改变。
补充
这段测试代码中,用来测试的返回值是基本数据类型,最后返回值并没有 finally 语句块被修改。
但是,当返回值为引用类型时,finally 语句块是可以修改最后的返回值的。举个例子:
定义一个雇员类
public class Employee {
private int salary;
public Employee(int salary) {
this.salary = salary;
}
//省略getter和setter
}
尝试在 finally 语句块中修改雇员的状态
public static Employee test() {
Employee emp = new Employee(100);
try {
throw new Exception();
}catch (Exception e) {
return emp;
}finally {
emp.setSalary(200);
}
}
public static void main(String[] args) {
System.out.println("emp.salary = " + test().getSalary());
}
输出结果
这是因为 emp 和 returnedValue 同时指向同一个对象,所以对 emp 的修改会影响到最终的返回值。(具体的原因可以参考我的上一篇文章:Java的参数传递到底是值传递还是引用传递.)