Java
异常的基本概念,Throwable
、异常处理关键字:try-catch-finally、throw、throws
;本篇文章我们将更加深入的了解 finally
在异常处理中的常见问题和底层原理。Java 8
finally
中的陷阱try 或 catch
中存在 return
,finally
都会执行,下面我们来看看下面几种场景: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;
@Override
public 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;
@Override
public 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.IllegalStateException
at 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: Exception
at 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;
}
}
}
实际上,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编程。
我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
保持关注我的博客,让我们共同追求技术卓越。