Java 中的 finally 代码块不执行的情况
by emanjusaka from https://www.emanjusaka.top/2024/07/java-finally-non-execution 彼岸花开可奈何
本文为原创文章,可能会更新知识点以及修正文中的一些错误,全文转载请保留原文地址,避免未即时修正的错误误导。
一、前言
先抛出一个问题:Java 中的 finally 代码块一定会被执行吗?
这是一个比较常见的面试题,在我们的印象中好像 finally 的代码块是一定会被执行的。但真实的情况是这样的吗?其实答案是否定的,有些情况下它是不被执行的。下面我们来盘点下 finally 代码块不会执行的情况。
二、finally 不执行的情况
1、在执行 try 块之前就直接 return 的情况
package top.emanjusaka.trycatch;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @Author emanjusaka
* @Date 2024/7/16 14:08
* @Version 1.0
*/
@SpringBootTest
public class TryCatchTest {
private int tryReturnBefore() {
int i = 0;
if (i == 0) {
return ++i;
}
try {
System.out.println("执行了 try");
} catch (Exception e) {
System.out.println(e);
} finally {
System.out.println("执行了 finally");
}
return i;
}
@Test
void testReturn() {
System.out.println("输出了 i = " + tryReturnBefore());
}
}
执行结果:
在上面的代码中执行的最终结果并没有输出“执行了 finally”,这说明代码并没有执行 finally 的代码块甚至连 try 块也没有执行。因为在 try 块之前代码就已经 return 了,代码到此就会终止下面的 try...catch...finally 都不会执行了。这就是 finally 块不执行的情况之一。现在我们想想如果在 try 块执行之前没有 return 而是出现了 error会怎样呢?还会执行下面的 finally 块吗?
2、在执行 try 块之前出现了错误
private void tryErrorBefore() {
int i = 0;
System.out.println("输出了 i = " + i / 0);
try {
System.out.println("执行了 try");
} catch (Exception e) {
System.out.println(e);
} finally {
System.out.println("执行了 finally");
}
}
@Test
void testError() {
tryErrorBefore();
}
执行结果:
这个结果就回答了上面的问题,如果在 try 块执行之前出现了 error,finally 代码块依然是不会执行的。这和上面的情况差不多是同样的道理,代码都是在进入 try 块之前就终止了,只不过一个是 return 了一个是出现了 error。那如果代码执行到了try 块中了呢,这时候 finally 代码块一定会执行了吗?答案同样是否定的,下面介绍一种同样特殊的情况。
3、在执行 try 块之中退出 jvm
@Test
void testExit() {
int i = 0;
try {
System.out.println("执行了 try");
System.out.println("输出了 i = " + i);
System.exit(0);
} finally {
System.out.println("执行了 finally");
}
}
执行结果:
看到执行结果后,我们发现即使执行了 try 块,但是 finally 代码块同样没有执行。这是因为代码执行到System.exit(0)
时,语句会立即终止当前运行的 Java 虚拟机,程序会立即退出,不会继续执行后续的代码,包括 finally 代码块。
上面的三种情况可能是比较极端,但也是可以作为在出现 finally 没有执行的情况下的排查方向的。
三、分析finally 的执行顺序
在上面我们盘点了三种 finally 代码块不会执行的情况。下面我们来研究下 finally 代码块的执行顺序是怎样的?
它是在 try 块之前执行?还是之后执行?如果再加上 catch 块它们的执行顺序又是什么呢?
private int finallyOrder() {
int i = 0;
try {
System.out.println("执行了 try");
return ++i;
} finally {
System.out.println("执行了 finally");
}
}
@Test
void testOrder() {
System.out.println("输出了 i = " + finallyOrder());
}
这段代码的输出的顺序是啥?
执行了 try
执行了 finally
输出了 i = 1
可以看出 finally 代码块是在 try 块方法返回之前执行的。如果 try...catch捕获到异常了,finally 代码块的执行时机是在哪呢?
private int finallyExceptionOrder() {
int i = 0;
try {
System.out.println("执行了 try");
return i / 0;
} catch (Exception e) {
System.out.println("捕获了异常");
return i + 1;
} finally {
System.out.println("执行了 finally");
}
}
@Test
void testOrder() {
System.out.println("输出了 i = " + finallyExceptionOrder());
}
执行结果为:
执行了 try
捕获了异常
执行了 finally
输出了 i = 1
同样从结果可以看出,finally 代码块是在 catch 中方法返回之前执行的。
现在我们想一下这个操作,在 finally 代码块中改变变量 i 的值会对 try 块或者 catch 块中返回值造成影响吗?
四、几种finally 块中的返回值的情况
private int finallyChangeVariable() {
int i = 0;
try {
System.out.println("执行了 try");
return i + 1;
} finally {
++i;
System.out.println("执行了 finally");
}
}
@Test
void testOrder() {
System.out.println("输出了 i = " + finallyChangeVariable());
}
我们预想中结果应该是:
执行了 try
执行了 finally
输出了 i = 2
但实际真的是这样的吗?答案当然是 no。实际上输出的 i 依然是 1:
执行了 try
执行了 finally
输出了 i = 1
这是什么原因呢?按照上面的正常逻辑首先执行 try 块然后在方法返回之前去执行 finally 块,在 finally 块中不是对 i 进行了自增吗?怎么输出的结果还是 1。
这是因为Java程序会把try或者catch块中的返回值保留,也就是暂时的确认了返回值,然后再去执行finally代码块中的语句。等到finally代码块执行完毕后,如果finally块中没有返回值的话,就把之前保留的返回值返回出去。
如果 return 方法不在 try 或者 catch 块中呢,finally 块对变量的改变会有作用吗?
return 方法在 finally 块中
private int finallyChangeVariable() { int i = 0; try { System.out.println("执行了 try"); } finally { ++i; System.out.println("执行了 finally"); return i + 1; } } @Test void testOrder() { System.out.println("输出了 i = " + finallyChangeVariable()); }
return 方法 try...catch...finally 之外
private int finallyChangeVariable() { int i = 0; try { System.out.println("执行了 try"); } finally { ++i; System.out.println("执行了 finally"); } return i + 1; } @Test void testOrder() { System.out.println("输出了 i = " + finallyChangeVariable()); }
我们发现上面两种情况结果是一样的:
执行了 try
执行了 finally
输出了 i = 2
finally 块对变量的改变都发挥了作用。通过上面我们发现在finally块中进行return操作的话,则方法整体的返回值就是finally块中的return返回值。如果在finally块之后的方法内return,则return的值就是进行完上面的操作后的return值。
在 try 块和 finally 块中都有 return 方法时,最终执行的是 try 块中的 return 方法还是 finally 块中的方法呢?
private int finallyChangeVariable() {
int i = 0;
try {
System.out.println("执行了 try");
return i + 1;
} finally {
System.out.println("执行了 finally");
return i + 2;
}
}
@Test
void testOrder() {
System.out.println("输出了 i = " + finallyChangeVariable());
}
最终执行的是 finally 块中的 return 方法。原因其实上面已经说了 finally 块是在 try 块中 return 方法之前执行的,执行到 finally 块中的 return 方法就返回并终止并不会再去执行 try 中的 return 方法。
在技术的星河中遨游,我们互为引路星辰,共同追逐成长的光芒。愿本文的洞见能触动您的思绪,若有所共鸣,请以点赞之手,轻抚赞同的弦。
原文地址: https://www.emanjusaka.top/2024/07/java-finally-non-execution
微信公众号:emanjusaka的编程栈