Java 知己
try...catch...finally 恐怕是大家再熟悉不过的语句了, 而且感觉用起来也是很简单, 逻辑上似乎也是很容易理解. 不过, 我亲自体验的 "教训" 告诉我, 这个东西可不是想象中的那么简单, 听话. 不信?
看几个例子, 回顾一下执行顺序
例子 1 无异常, finally 中的 return 会导致提前返回
- public static String test() {
- try {
- System.out.println("try");
- return "return in try";
- } catch(Exception e) {
- System.out.println("catch");
- return "return in catch";
- } finally {
- System.out.println("finally");
- return "return in finally";
- }
- }
调用 test() 的结果:
- try
- finally
- return in finally
例子 2 无异常, try 中的 return 会导致提前返回
- public static String test() {
- try {
- System.out.println("try");
- return "return in try";
- } catch(Exception e) {
- System.out.println("catch");
- } finally {
- System.out.println("finally");
- }
- return "return in function";
- }
调用 test() 的结果:
- try
- finally
- return in try
例子 3 有异常, finally 中的 return 会导致提前返回
- public static String test() {
- try {
- System.out.println("try");
- throw new Exception();
- } catch(Exception e) {
- System.out.println("catch");
- return "return in catch";
- } finally {
- System.out.println("finally");
- return "return in finally";
- }
- }
调用 test() 的结果:
- try
- catch
- finally
- return in finally
例子 4 有异常, catch 中的 return 会导致提前返回
- public static String test() {
- try {
- System.out.println("try");
- throw new Exception();
- } catch(Exception e) {
- System.out.println("catch");
- return "return in catch";
- } finally {
- System.out.println("finally");
- }
- }
调用 test() 的结果:
- try
- catch
- finally
- return in catch
例子 4 有异常, 不会提前返回
- public static String test() {
- try {
- System.out.println("try");
- throw new Exception();
- } catch(Exception e) {
- System.out.println("catch");
- } finally {
- System.out.println("finally");
- }
- return "return in function";
- }
调用 test() 的结果:
- try
- catch
- finally
- return in function
小结
上面这几个例子, 大多数人已经非常了解. 同时也衍生出一些理论, 比如不要在 finally 中 return 等, 不再赘述.
再看几个例子, 返回值是否符合你的预期?
例子 1
- public static int test() {
- try {
- return 1;
- } finally {
- return 2;
- }
- }
返回值: 2
说明: 与我们上面的例子一致, finally 中的 return 导致提前返回, try 中的 return1 不会被执行.
附编译后的代码:
- public static int test() {
- try {
- boolean var0 = true;
- return 2;
- } finally {
- ;
- }
- }
可以看到编译器做过优化, 同时验证了 boolean 类型在底层是用 int 实现的, 但注意你在源码中直接给 int 行赋值 true 或 false 是不被允许的.
例子 2
- public static int test() {
- int i;
- try {
- i = 3;
- } finally {
- i = 5;
- }
- return i;
- }
返回值: 5
说明: 执行 try 中的代码后, 再执行 finally 中的代码, 最终 i 被赋值为 5, 最后返回
附编译后的代码:
- public static int test() {
- boolean var0 = true;
- byte i;
- try {
- var0 = true;
- } finally {
- i = 5;
- }
- return i;
- }
同样可以看出, 编译器做了一些优化.
正是金九银十跳槽季, 为大家收集了 2019 年最新的面试资料, 有文档, 有攻略, 有视频. 有需要的同学可以在公众号 [Java 知己] , 发送 [面试] 领取最新面试资料攻略!
例子 3
- public static int test() {
- int i = 1;
- try {
- i = 3;
- return i;
- } finally {
- i = 5;
- }
- }
返回值: 3
这个例子稍微有点意思, 按我们通常的思维, 应该还是返回 5, 毕竟 finally 中把 i 赋值为 5 了嘛, 然后由 try 中的 return 返回. 然而很不幸, 返回值是 3.
为什么呢? 先看一下编译后的代码:
- public static int test() {
- boolean var0 = true;
- byte var1;
- try {
- int i = 3;
- var1 = i;
- } finally {
- var0 = true;
- }
- return var1;
- }
我们会发现, finally 中的代码块不起作用. 不知你是否想起一点: Java 中是按值传递的, finally 中的 i 只是一个局部变量, finally 块执行完毕后, 局部变量便不复存在.
接着看例子:
例子 4
- public static List test() {
- List<Integer> list = new ArrayList<>();
- try {
- list.add(1);
- return list;
- } finally {
- list.add(2);
- }
- }
返回: 包含 1 和 2 两个元素的 List 对象.
说明: 这个例子中, 基本类型 int 被替换为引用类型 List, 虽然 list 是按值传递, 但它内部的状态可变 (体现在这里, 就是可以 add 元素). 扩展: finally 只能保证对象本身不可变, 但无法保证对象内部状态不可变.
附编译后的代码:
- public static List test() {
- ArrayList list = new ArrayList();
- ArrayList var1;
- try {
- list.add(1);
- var1 = list; // 执行这一步操作后, var1 和 list 指向同一个对象
- } finally {
- list.add(2);
- }
- return var1;
- }
你现在应该觉得自己理解了, 那么再来看两个例子:
例子 5
- public static int test() {
- try {
- System.exit(0);
- } finally {
- return 2;
- }
- }
该函数没有返回值. 原因: jvm 提前退出了.
附编译后的代码:
- public static int test() {
- try {
- System.exit(0);
- return 2;
- } finally {
- ;
- }
- }
例子 6
- public static int test() {
- try {
- while(true) {
- System.out.println("Infinite loop.");
- }
- } finally {
- return 2;
- }
- }
由于 try 中的无限循环阻塞, 永远执行不到 finally 中的代码块.
附编译后的代码:
- public static int test() {
- try {
- while(true) {
- System.out.println("Infinite loop.");
- }
- } finally {
- ;
- }
- }
小结
为了方便说明, 只举了 finally 代码块的例子, catch 代码块是类似的.
总结
执行顺序:
1. try 代码块中 return 前面的部分
2. catch 代码块中 return 前面的部分
3. finally 代码块中 return 前面的部分
4. finally 的 return 或 catch 的 return 或 try 的 return. 若前面的 return 被执行, 会导致提前返回, 同时后面的 return 被忽略.
5. 方法的其他部分
变量:
注意 Java 的按值传递规则
特殊情况:
注意 finally 不会被执行的情况
"不积跬步, 无以至千里", 希望未来的你能: 有梦为马 随处可栖! 加油, 少年!
来源: https://www.qcloud.com/developer/article/1509064