finally 中代码一定会被执行吗?
finally 中代码一定会被执行吗?
zhangchangsheng面试考察点
- 异常处理机制理解:面试官不仅仅是想知道 “会” 或 “不会”,更是想考察你对 Java 异常处理机制的深入理解,包括
try-catch-finally的执行流程和字节码层面的实现。 - 边界情况掌握:考察你是否了解
finally不执行的特殊场景,如System.exit()、线程异常终止等,这是区分 “会用” 和 “精通” 的关键点。 - 代码执行顺序理解:看你是否清楚
finally与return语句的执行顺序,以及finally中修改返回值的影响,这是常见的面试陷阱。
核心答案
答案是:不一定。finally 中的代码在 绝大多数情况下 会执行,但存在 特殊情况 会导致 finally 不执行。 **** **** ****
| 场景分类 | finally 是否执行 | 说明 |
|---|---|---|
| 正常执行完 try | ✅ 执行 | 最常见情况 |
| try 中抛出异常 | ✅ 执行 | catch 处理后执行 finally |
| try 中有 return | ✅ 执行 | return 前执行 finally |
| catch 中有 return | ✅ 执行 | return 前执行 finally |
| 调用 System.exit() | ❌ 不执行 | JVM 直接退出 |
| 线程死亡 | ❌ 不执行 | 线程被杀死 |
| 断电/系统崩溃 | ❌ 不执行 | 物理层面的不可抗力 |
| 死循环/死锁 | ⏸️ 无法到达 | 理论上会执行,但永远到不了 |
一句话概括:只要 JVM 正常运行且线程存活,finally 就一定会执行。
深度解析
一、finally 的正常执行场景
在绝大多数情况下,finally 都会执行。无论 try 块中是正常执行、抛出异常,还是有 return 语句。
flowchart TB
T["try 块<br/>1. 正常执行代码<br/>2. 可能抛出异常<br/>3. 可能有 return 语句"]
T --> OK["正常完成"]
T --> EX["抛出异常"]
EX --> C["catch 块<br/>处理异常"]
OK --> F["finally 块<br/>必定执行"]
C --> F
上图展示了 finally 的正常执行流程。无论从 try 块的哪个路径出来(正常完成、异常抛出、return 返回),都会先经过 finally 块。
// 场景一:正常执行 |
二、finally 不执行的特殊场景
虽然 finally 几乎总会执行,但以下几种情况 不会执行:
flowchart TB
A["try 块执行"] --> B["遇到 return<br/>计算返回值(存入临时变量)"]
B --> C["执行 finally<br/>(return 之前执行)"]
C --> D["真正 return<br/>使用之前计算的返回值"]
上图列出了 finally 不执行的所有场景,下面详细说明:
System.exit()调用:这是最常见的finally不执行场景。System.exit()会立即终止 JVM,所有代码都不会继续执行。注意System.exit()内部调用Runtime.getRuntime().exit(),会触发 shutdown hooks,但不会执行 finally。- 线程死亡:如果线程在执行
try块时被强制终止(如Thread.stop(),已废弃),finally不会执行。但正常的中断(interrupt())不会阻止finally执行。 - 断电/系统崩溃:这是物理层面的不可抗力,JVM 进程被强制终止,所有代码都无法执行。
finally块本身抛出异常:finally会开始执行,但如果finally块内部抛出未捕获的异常,finally剩余的代码不会执行,异常会向外传播。
// System.exit() 导致 finally 不执行 |
三、finally 与 return 的执行顺序
这是面试中的高频考点,需要理解 finally 和 return 的执行顺序。
| 场景 | 说明 | 示例 |
|---|---|---|
| System.exit() 调用 | JVM 直接退出,finally 永远不会执行 | try { System.exit(0); } finally { // 永远不会执行 } |
| 线程死亡 | 线程被 stop() 或外部杀死(stop 已废弃,但会阻止 finally) | thread.stop(); |
| 断电/系统崩溃 | 物理层面的不可抗力 | 拔电源、系统崩溃等 |
| finally 块本身抛出异常 | finally 中抛出异常后,之后的代码不会执行 | finally { throw new RuntimeException(); } |
| 无限循环/死锁 | try 中死循环,理论上 finally 会执行,但永远到不了 | try { while (true) {} } finally { ... } |
上图展示了 finally 与 return 的执行顺序,关键点在于:
- 返回值在
finally执行前就已经计算并存储:Java 会把return的值存入临时变量 finally执行后,使用之前存储的返回值:即使finally修改了局部变量,返回值也不变- 唯一例外:
finally中有新的return语句,会覆盖原来的返回值
// 经典面试题:返回值是多少? |
四、finally 中的 return:强烈不推荐
虽然可以在 finally 中使用 return,但这是 极其糟糕的实践,会导致多个问题:
// finally 中的 return 会覆盖 try/catch 的 return |
最佳实践:永远不要在 finally 块中使用 return 语句。这会导致代码难以理解和调试,还会吞掉异常。
五、字节码层面的理解
从字节码层面看,finally 的实现原理是将 finally 块的代码复制到所有可能的出口路径上:
// 源代码 |
这也是为什么 finally 总会执行——因为它的代码被插入到了所有可能的执行路径中。
面试高频追问
try块中有return,finally还会执行吗? 会。finally在return之前执行,但返回值已经确定。finally中修改返回值有效吗? 对于基本类型无效(返回值已存入临时变量),对于引用类型的引用也无效,但可以修改对象内容。System.exit()和return有什么区别?return是方法返回,finally会执行;System.exit()是 JVM 退出,finally不会执行。- 为什么不要在
finally中写return? 会覆盖原始返回值,还会吞掉异常,导致问题难以排查。
常见面试变体
- “什么情况下
finally不会执行?” - “
try里有return,finally的执行时机是什么?” - “如果在
finally中修改返回值,会生效吗?” - “
System.exit(0)会影响finally执行吗?”
记忆口诀
finally 三原则:
- 正常必执行:只要 JVM 活着,finally 总会跑
- exit 也不行:System.exit 一调用,finally 没戏唱
- return 前执行:return 前先 finally,返回值已定好
总结
finally 中的代码在 绝大多数情况下会执行,只有在 JVM 退出(System.exit())、线程死亡、系统崩溃等极端情况下才不会执行。finally 的执行时机在 return 之前,但 return 的值在 finally 执行前就已确定。强烈不建议 在 finally 中使用 return,这会覆盖返回值并吞掉异常。
声明:本文转载自小哈的博客 《finally 中代码一定会被执行吗?》,版权归原文作者所有。转载请保留本声明与原文链接。
