在 Java 中,无论其前面的 try
代码块是否抛出异常,finally
都会执行到,所以它通常被用来关闭资源或进行清理操作。但是确实是存在一些特殊情况,导致 finally
是不会执行到的。
详解
1、在 try 语句之前 return 或者抛出异常
@Test
public void finallyTest() {
int i = 1 / 0;
try {
//....
} finally {
System.out.println("执行finally");
}
}
finally
语句被执行的必要而非充分条件是相应的 try 语句一定被执行到。
2、程序所在的线程死亡
序所在的线程在进入 finally
代码块之前死亡,例如调用 System.exit(0)
,finally
代码块不会被执行。
@Test
public void finallyTest() {
try {
System.out.println("执行 try 代码块");
System.exit(0);
} finally {
System.out.println("执行finally");
}
}
System.exit(0)
是终止Java虚拟机的,JVM都停止了,自然所有的程序就都结束了,当然finally
语句也就不会被执行了。
3、守护线程
如果 finally
代码块是在守护线程中,并且所有的非守护线程都已经退出,那么JVM可能在守护线程执行 finally
代码块之前退出。
public class FinallyTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "--执行守护线程");
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
System.out.println(Thread.currentThread().getName() + "--执行守护线程 finally 代码块");
}
});
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-退出");
}
}
结果:
main 线程退出后,JVM 就会退出了,finally
就执行不到了。
扩展
下面我们通过一些示例来看看 try
、catch
、finally
代码块的执行顺序。
示例一
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
}
执行结果:
finally
代码块在 try
代码块 return 语句前执行。
示例二
基于上面例子改造下:
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
int i = 1 / 0;
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
}
执行结果:
finally
代码块在 catch
代码块 return 语句前执行。
示例三
就要示例一,我们再改造下:
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return 1;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
return 2;
}
}
}
执行结果:
你会看到 finallyTest()
返回的是 finally
代码块的 2。这是为什么呢?这里先不解释,继续看你就会慢慢明白了。
示例四
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
int i = 1;
try {
System.out.println("try 代码块被执行!");
return i;
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
i++;
System.out.println("finally 代码块中的 i=" + i);
}
}
}
执行结果
finally
代码中的 i = 2 ,但是返回的 1。是不是有点儿奇怪,前面介绍了 finally
代码块是在 return 之前执行的,但是在 finally
中 i = 2,为什么返回的还是 1 呢?
当程序执行到 try
代码块的 return
语句时,返回值会被存储起来,但在实际返回给调用者之前,finally
代码块会被执行。如果 finally
代码块中也有一个 return
语句,那么它将覆盖 try
代码块中的返回值,并且是最终的返回结果。
示例五
public class FinallyTest {
public static void main(String[] args) {
System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
}
public static int finallyTest() {
try {
System.out.println("try 代码块被执行!");
return tryTest();
} catch (Exception e) {
System.out.println("catch 代码块被执行!");
return 3;
} finally {
System.out.println("finally 代码块被执行!");
}
}
public static int tryTest() {
System.out.println("tryTest 代码块被执行!");
return 3;
}
}
执行结果
这结果有没有出乎你的意料?前面不是提过,finally
里面的代码块不是比 try
代码库里面的 return 先执行吗?怎么会出现 tryTest()
比 finally
还先执行呢?原因如下:
在Java中,当一个try
代码块包含一个return
语句时,这个return
语句会“先执行”,但并不是立即返回。这里的“先执行”指的是确定返回值的过程会在进入finally
代码块之前发生。然而,实际上在finally
代码块执行完毕之前,方法不会真正返回。
最后
所以 try
里面有 return
语句时,执行过程如下:
- 执行 try 代码块。
- 执行
try
里面的return
。当程序执行到try
代码块中的return
语句时,会先计算这个return
语句的返回值。例如,如果返回的是一个变量的值,那么这个值会被确定下来。所以示例5 中的tryTest()
是先于finally
执行的。 - 暂停返回:尽管
try
代码块中的return
语句已经“执行”了,但方法不会立即返回。而是转移到finally
代码块。 - 执行
finally
代码块:finally
代码块现在执行。如果finally
代码块中也有return
语句,它会覆盖try
代码块中的返回值。 - 完成方法返回:如果
finally
代码块没有新的return
语句,那么最初在try
代码块中确定的返回值将被用作方法的最终返回值。如果finally
代码块中有return
语句,那么它的返回值会成为最终结果。
所以,记住这两点即可:
try
代码块永远比finally
代码库先执行。finally
的 return 会覆盖try
的return。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。