前言
本文讲解过于繁琐,已重新修改至 关于 Java finally 执行顺序 -- 修改版。
原题
首先来看看这道题:
1 | public class test1 { |
执行结果是 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 返回之前执行。
1 | public class FinallyTest1 { |
运行结果:
try block
finally block
b>25, b = 100
100
可以根据运行结果得知,在 finally
语句块执行之前,return b += 80;
中的 b + =80;
这部分已经运行,但是没有返回,然后等 finally
执行完以后才返回结果的。
例二
如果觉得这个 例 1
还不足以说明这个情况的话,下面再加个例子加强证明结论:
1 | public class FinallyTest1 { |
运行结果:
try block
return statement
finally block
after return
这下总明白了吧,执行 finally
之前将 return
后的 test12()
方法先执行了,然后再执行的 finally
语句。
拓展问题一
前面两个例子证明了原题得出的结论,但是这个结论好像还不能足矣证明原题中的结果为什么是"ABAB"
,还有一个新问题:
如果,finally 中对 return 返回的变量进行了修改,那么 return 返回的是修改前的值还是修改后的值?
我们在 例 1
的 finally
语句块中加上一句 b = 10;
,那么结果又会是什么呢?
1 | public class FinallyTest2 { |
运行结果:
try block
finally block
b>25, b = 100
100
可以看到 return
的值并没有被 finally
中的语句改变,其实如果这样得出结论:finally 中对 return 返回的变量进行了修改,并不会影响 try 中 return 的值,是不负责任的,因为我们只考虑了基本数据类型,如果是引用数据类型,还会是这种结果么?
扩展问题二
接 拓展问题 1
的又一个问题,对于引用数据类型的情况:
1 | public class FinallyTest3 { |
运行结果:
Finally
可以看到,对于基本数据类型和引用数据类型,结论是不同的,再次总结下结论吧:
- 对于基本数据类型,
finally
语句块中对try
语句块的return
的变量进行了修改,return
的则是修改前的值。 - 对于引用数据类型,
finally
语句块中对try
语句块的return
的变量进行了修改,return
的则是修改后的值。
看到这里,不知道你有没有想到这个结论与 Java 的方法调用时的所谓的 值传递
和 引用传递
的结论有些类似。
既然得出了这个结论,那么这个结论是必然的情况么?一定正确么?别急,来看下一个例子。
拓展问题三
对于 拓展问题 2
我再做一个增强版的,其实就是在第 17 行之下添加一条语句 stu = null
:
1 | public class FinallyTest4 { |
运行结果:
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 语句直接返回。
本文参考
- Java finally语句到底是在return之前还是之后执行?
- finally执行顺序面试题