"finally 块里不要写 return"是一句常见的代码规范建议,但只有真实跑过一次,才能真切感受到它有多"安静"——调用方拿到的是一个完全正常的返回值,连一点异常的痕迹都看不到。
上一篇:enum.values() 每次调用都真实克隆一个新数组:真实测出循环里调用慢了近 2 倍
最基础的一步:try 块已经执行了 return,finally 块紧接着又执行了一次 return,最终真实生效的是哪一个。
FinallyReturnDemo.java static int returnOverride() { try { return 1; } finally { return 2; // 真实覆盖了 try 里的返回值 } }
returnOverride() = 2
真实结果是 2,不是 1。JVM 层面的真实机制是:try 块里的 return 1 会先把返回值 1 暂存起来,但在真正把控制权交还给调用方之前,还要先执行 finally 块;finally 块里如果也执行了 return,这次新的返回指令会真实、完全地替换掉之前暂存的那个值——不是"两个值取一个更靠后生效的抽象规则",而是字节码层面真实的控制流覆盖。
如果 try 块里不是 return,而是抛出一个异常,finally 块里的 return 会发生什么?
FinallyReturnDemo.java static int exceptionSwallowed() { try { throw new RuntimeException("try 里抛的异常"); } finally { return 99; // 真实吞掉了上面的异常 } }
exceptionSwallowed() 真实返回 = 99(try 里明明抛了异常,但因为 finally 里也 return 了,异常被真实吞掉,调用方拿到的是正常返回值)
这是真实测出来最危险的一种情况:调用方完整地调用了 exceptionSwallowed(),拿到一个正常的返回值 99,完全不知道方法内部曾经真实抛出过一个异常——没有 catch 块,没有日志,异常对象就这样在 finally 的 return 面前真实消失了。这不是异常处理不完善,而是 Java 语言规范明确定义的行为:finally 块里的 return(或者 break/continue)会真实丢弃掉任何正在传播中的异常,不管这个异常是 try 里抛的,还是 catch 里抛的。
作为对照,确认问题的根源确实是 finally 里的 return 本身,而不是"finally 会在 return 之后执行"这件事本身。
FinallyReturnDemo.java static int normalCase() { int x = 1; try { return x; } finally { x = 2; // 只是改了局部变量 } }
normalCase() = 1
每一步都是真实运行这三个方法时,JVM 对返回值槽位和异常传播真实做的事情。