文章目录
- 前言
- 版本
- `finally` 中的陷阱
- finally 中使用 return
- `finally` 中修改数据的影响
- 基本类型
- 引用类型
- `finally` 中的代码 “非最后” 执行
- `finally` 代码块一定会执行?
- 异常丢失
- finally 底层原理分析
- 总结
- 个人简介
前言
- 在上一篇文章中,我们介绍了
Java
异常的基本概念,Throwable
、异常处理关键字:try-catch-finally、throw、throws
;本篇文章我们将更加深入的了解finally
在异常处理中的常见问题和底层原理。
版本
Java 8
finally
中的陷阱
- 我们知道无论是否发生异常还是
try 或 catch
中存在return
,finally
都会执行,下面我们来看看下面几种场景:
finally 中使用 return
- 当我们在
finally
中使用return
时,try
或catch
中的return
会失效或异常丢失(见下文),会在finally
直接返回。
public class Main {public static void main(String[] args) {System.out.println(extracted());}private static int extracted() {int a = 1;try {a = 2;a = a / 0;return a;} catch (Exception e) {System.out.println(e);return a;} finally {System.out.println("this is finally");return -1;}}
}// 输出 finally 中直接 return -1
java.lang.ArithmeticException: / by zero
this is finally
-1
finally
中修改数据的影响
- 如果你在
finally
代码块中修改了数据,你可能会有一些奇妙的体验。
基本类型
public class Main {public static void main(String[] args) {System.out.println(extracted());}private static int extracted() {int a = 1;try {a = 2;return a;} finally {System.out.println("this is finally");a += 3;}}
}// 输出
this is finally
2
- 我们可以得出结论在
finally
中修改基本类型不会影响try 、catch
中return
中的返回值(但是会影响finally
中的return
,见下面的案例)。
public class Main {public static void main(String[] args) {System.out.println(extracted());}private static int extracted() {int a = 1;try {a = 2;return a;} finally {System.out.println("this is finally");a += 3;return a;}}
}// 输出
this is finally
5
引用类型
// 案例一
public class Main {public static void main(String[] args) {System.out.println(extracted());}private static Object extracted() {Person person = new Person();try {return person;} finally {System.out.println("this is finally");person.age = 5;}}
}class Person {int age;@Overridepublic String toString() {return "Person age= " + age;}
}// try 中的 return 被修改
this is finally
Person age= 5// 案例二
public class Main {public static void main(String[] args) {System.out.println(extracted());}private static Object extracted() {Person person = new Person();try {return person;} finally {System.out.println("this is finally");person = (new Person());person.age = 3;}}
}class Person {int age;@Overridepublic String toString() {return "Person age= " + age;}
}// try 中的 return 没有被修改
this is finally
Person age= 0
- 上面的结果看着有点奇怪但实际上很好理解,我们在以前的文章中讲过,
Java 实际上只有值传递而不存在引用传递
,当为返回值为引用类型时,返回的其实是一个地址,在案例一中我们使用地址修改了原内容,而在案例二中,我们其实将 person 指向了新的地址(new Person()),因此并没有修改原返回值地址的内容。
finally
中的代码 “非最后” 执行
- 有时候我们发现
finally
中的代码 “非最后” 执行,那么有可能是并行执行了,比如:
public class Main {public static void main(String[] args) {extracted();}private static void extracted() {try {throw new IllegalStateException();} catch (Exception e) {e.printStackTrace();} finally {System.out.println("this is finally");}}
}// 比较难出现
this is finally
java.lang.IllegalStateExceptionat Main.extracted(Main.java:9)at Main.main(Main.java:4)
- 实际上是因为
e.printStackTrace() 使用的是 System.err,而 System.out.println 使用的是 System.out
,标准输出流和标准错误输出流是彼此独立执行的,且JVM
为了高效的执行会让二者并行运行,所以会出现finally 中的代码 “非最后” 执行的场景。
finally
代码块一定会执行?
- 虽然这里有一定抬杠的嫌疑,但实际上确实有一些场景下
finally
代码块不会执行,比如:
在 try-catch 语句中执行了 System.exit
在 try-catch 语句中出现了死循环
在 finally 执行之前 JVM 崩溃
- 在
try-catch
语句中执行了System.exit
public class Main {public static void main(String[] args) {extracted();}private static void extracted() {try {// 此代码块执行完程序退出System.exit(0);throw new IllegalStateException();} catch (Exception e) {e.printStackTrace();} finally {System.out.println("this is finally");}}
}
异常丢失
- 如果我们在
finally
代码块中抛出异常或使用retrun
,将会导致我们try-catch
中的异常丢失。
// 案例一
public class Main {public static void main(String[] args) throws Exception {extracted();}private static void extracted() throws Exception {try {throw new IllegalStateException();} finally {throw new Exception("Exception");}}
}// 输出
Exception in thread "main" java.lang.Exception: Exceptionat Main.extracted(Main.java:11)at Main.main(Main.java:4)// 案例二
public class Main {public static void main(String[] args) throws Exception {extracted();}private static int extracted() throws Exception {try {throw new IllegalStateException();} finally {return 1;}}
}
finally 底层原理分析
- 《The JavaTM Virtual Machine Specification, Second Edition》 一书中我们可以知道 Java 虚拟机是如何编译 finally:
实际上,Java 虚拟机会把 finally 语句块作为 subroutine 直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值(基本类型值或地址)到本地变量表(Local Variable Table)中,待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。
- 理解了
JVM
对finally
的实现,我们其实就很好理解finally 中修改数据的影响
中的案例,有兴趣的朋友可以下去深入了解。
总结
- 本文我们结合了
finally
在实际使用中可能出现的问题并进行分析对应的原因,最后介绍了finally
在JVM
中的实现原理,帮助我们在日常开发的更好的使用finally
,下篇文章将会介绍实际异常处理中的一些最佳实践。
个人简介
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。
🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。
💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。
🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
📖 保持关注我的博客,让我们共同追求技术卓越。