钩子:一道题,测出你的水平
static int test() {
try {
return 1;
} finally {
return 2;
}
}
调用 test() 返回几?先把答案默念一遍,再看下去——这道题在面试里筛掉过无数人。
核心:执行顺序铁律
先看基础版顺序。没有异常:try → finally;有异常且接住了:try(出事那行中断)→ catch → finally。
try {
System.out.println("try: 开工");
int r = 10 / 0; // 这行之后 try 中断
return 1; // 到不了
} catch (ArithmeticException e) {
System.out.println("catch: 接住 " + e.getMessage());
return 2;
} finally {
System.out.println("finally: 无论出没出事,我都执行");
}
输出:try 的前半句 → catch → finally。
finally 是"临走前必办的手续"——不管 try 是正常走完、被 catch 接住、甚至里面已经 return 了,它都执行。所以关流、解锁、还连接这类"走之前必须做的事",写在 finally 里。

正题:return 拦不住 finally
把开头那道题换个温和的版本:
static int returnInTry() {
try {
return 1; // 返回值 1 被"记住",但先不出方法
} finally {
System.out.println("我还是执行了"); // 必打印
}
}
return 已经出发,finally 依然执行——Java 的规矩是:return 时先把返回值"揣进兜里",走完 finally 才真正出门。
那开头那道题呢?finally 里也写了 return,兜里的 1 会被换成 2——返回 2。
更狠的是:如果 try 里抛了异常,finally 的 return 会把异常一起吞掉——调用方拿不到异常、看不到报错,以为一切正常。线上排查这种问题,能查一宿。
所以铁律是:finally 只做清理(关流、解锁),不写 return,不抛异常。
坑点:finally 里改返回值,生效吗?
分两种,规则很"反直觉":
// 基本类型:不生效
static int changePrimitive() {
int result = 10;
try {
return result; // 10 被复印一份揣兜里
} finally {
result = 99; // 改的是复印件
}
} // 返回 10
// 对象:内容生效
static Box changeBox() {
Box box = new Box();
try {
return box; // 记住的是"地址"
} finally {
box.buffer += "(改过)"; // 顺着地址改了真身
}
} // 调用方看到改动
本质就是第 13 篇讲的:基本类型赋值是复印,对象赋值是传地址。return 揣进兜里的,一个是要值,一个是要地址。
速查表
| 场景 | 执行顺序 / 结果 |
|---|---|
| 无异常 | try → finally |
| 有异常且接住 | try(中断处)→ catch → finally |
| try 里 return | finally 仍执行,之后才真正返回 |
| finally 里 return | 覆盖 try 的返回值,还可能吞异常(禁写) |
| finally 改基本类型 | 不影响返回值(复印件) |
| finally 改对象内容 | 生效(同一地址) |
| finally 何时跳过 | 仅 System.exit / JVM 崩溃 |
本文示例代码均已本地实测通过,可直接运行。
对应文件:FinallyDemo.java
橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。