摘要try里有returnfinally里也有return最终返回哪个答案是finally的。更可怕的是finally里的return会吞掉try里的异常。一、问题现象publicclassTryFinallyTest{publicstaticvoidmain(String[]args){System.out.println(test());// 输出什么}publicstaticStringtest(){try{System.out.println(try);returntry的返回值;}finally{System.out.println(finally);returnfinally的返回值;// ❌ 覆盖了 try 的 return}}}运行结果try finally finally的返回值try里的return被finally里的return覆盖了再看一个更隐蔽的publicstaticvoidmain(String[]args){System.out.println(test2());// 输出什么}publicstaticStringtest2(){try{System.out.println(try);thrownewRuntimeException(try里的异常);}finally{System.out.println(finally);returnfinally吞掉了异常;// ❌ 异常被吞了}}运行结果try finally finally吞掉了异常RuntimeException被finally的return静默吞掉了二、踩坑现场场景 1finally 里修改返回值// ❌ 常见错误认知认为 finally 里对返回值的修改会生效publicstaticinttest(){intresult0;try{result1;returnresult;}finally{result2;// 对返回值的修改不生效}}System.out.println(test());// 1不是 2为什么return result在finally执行前已经把result的值1保存到了返回值槽位finally里修改的是局部变量不影响已经保存的返回值。例外如果返回的是引用类型且修改的是对象的内容不是重新赋值则会生效。publicstaticListStringtest(){ListStringlistnewArrayList();try{list.add(try);returnlist;}finally{list.add(finally);// ✅ 修改对象内容会影响返回值}}// 返回 [try, finally]场景 2finally 里的 return 吞异常// ❌ 生产代码里最危险的写法publicvoidprocessOrder(LongorderId){try{orderService.validate(orderId);orderService.process(orderId);}finally{// 清理资源lockService.unlock(orderId);return;// ❌ 如果这里写成 returntry 里的异常会被吞掉}}三、原理解析3.1 JVM 字节码视角try-finally在字节码层面是通过**异常表Exception Table**实现的。// 你写的代码try{return1;}finally{System.out.println(finally);}字节码等价于概念上try{intresult1;finallyBlock();// finally 在 return 前执行returnresult;}catch(Throwablee){finallyBlock();throwe;}关键规则finally块会在try的return之前执行但如果finally里也有return它会覆盖try的返回值。3.2 返回值保存机制try { return x; } │ ▼ 先把 x 的值保存到返回值槽位局部变量表的一个 slot │ ▼ 执行 finally 块 │ ├── finally 没有 return → 返回返回值槽位里的值 └── finally 有 return y → 返回 y覆盖3.3 异常被吞的字节码原理finally块里的return会导致方法正常返回JVM 不会再向外抛异常。try { throw new RuntimeException(); } │ ▼ 执行 finally 块 │ ├── finally 没有 return → 异常继续向外抛 ✅ └── finally 有 return → 方法正常返回异常被丢弃 ❌四、正确写法4.1 finally 里不要写 return// ❌ 错误publicstaticStringtest(){try{returntry;}finally{returnfinally;// 禁止}}// ✅ 正确finally 只做清理不返回值publicstaticStringtest(){try{returntry;}finally{System.out.println(清理资源);// 没有 return}}4.2 用 try-with-resourcesJava 7// ✅ 推荐自动关闭资源不需要手动写 finallytry(FileInputStreamfisnewFileInputStream(file.txt);BufferedReaderbrnewBufferedReader(newInputStreamReader(fis))){returnbr.readLine();}catch(IOExceptione){log.error(读取文件失败,e);returnnull;}// 不需要 finally资源自动关闭4.3 finally 里只做资源释放不写业务逻辑// ✅ 正确写法publicvoidprocess(Longid){LocklocklockService.getLock(id);lock.lock();try{// 业务逻辑doProcess(id);}finally{// 只做资源释放lock.unlock();}}4.4 如果 finally 里可能抛异常要单独 try-catch// ✅ 正确保证资源清理的异常不影响主流程的异常publicvoidprocess(){LocklocklockService.getLock();lock.lock();try{doProcess();}finally{try{lock.unlock();// unlock 也可能抛异常}catch(Exceptione){log.warn(释放锁失败,e);}}}五、最佳实践✅ finally 块的 3 条铁律finally 里永远不要写returnIDEA 会直接报警告finally 里只做资源释放不写业务逻辑finally 里抛出的异常会覆盖 try 里的异常要尽量处理掉 字节码验证返回值究竟怎么保存的可以用javap -c查看编译后的字节码javac Test.java javap-cTest.class你会看到astore/iload等指令在finally之前保存返回值。️ IDEA inspections 开启finally block does not complete normally→ 警告finally 块里有 return/throwreturn inside finally block→错误finally 里的 return六、小结finally在try的return之前执行finally里有return→ 覆盖try的返回值finally里有return→吞掉try里抛出的异常最危险finally 块的正确用途只有一种释放资源Java 7 优先用try-with-resources避免手写 finallyIDEA 里 finally 写 return 会标黄千万不要忽略这个警告下一篇预告泛型擦除为什么ListString和ListInteger是一家人—— 泛型只在编译期存在运行期被擦除了这个设计带来了哪些坑
【Java踩坑笔记】【基础语法篇】06_try-finally里的return,到底返回谁?
摘要try里有returnfinally里也有return最终返回哪个答案是finally的。更可怕的是finally里的return会吞掉try里的异常。一、问题现象publicclassTryFinallyTest{publicstaticvoidmain(String[]args){System.out.println(test());// 输出什么}publicstaticStringtest(){try{System.out.println(try);returntry的返回值;}finally{System.out.println(finally);returnfinally的返回值;// ❌ 覆盖了 try 的 return}}}运行结果try finally finally的返回值try里的return被finally里的return覆盖了再看一个更隐蔽的publicstaticvoidmain(String[]args){System.out.println(test2());// 输出什么}publicstaticStringtest2(){try{System.out.println(try);thrownewRuntimeException(try里的异常);}finally{System.out.println(finally);returnfinally吞掉了异常;// ❌ 异常被吞了}}运行结果try finally finally吞掉了异常RuntimeException被finally的return静默吞掉了二、踩坑现场场景 1finally 里修改返回值// ❌ 常见错误认知认为 finally 里对返回值的修改会生效publicstaticinttest(){intresult0;try{result1;returnresult;}finally{result2;// 对返回值的修改不生效}}System.out.println(test());// 1不是 2为什么return result在finally执行前已经把result的值1保存到了返回值槽位finally里修改的是局部变量不影响已经保存的返回值。例外如果返回的是引用类型且修改的是对象的内容不是重新赋值则会生效。publicstaticListStringtest(){ListStringlistnewArrayList();try{list.add(try);returnlist;}finally{list.add(finally);// ✅ 修改对象内容会影响返回值}}// 返回 [try, finally]场景 2finally 里的 return 吞异常// ❌ 生产代码里最危险的写法publicvoidprocessOrder(LongorderId){try{orderService.validate(orderId);orderService.process(orderId);}finally{// 清理资源lockService.unlock(orderId);return;// ❌ 如果这里写成 returntry 里的异常会被吞掉}}三、原理解析3.1 JVM 字节码视角try-finally在字节码层面是通过**异常表Exception Table**实现的。// 你写的代码try{return1;}finally{System.out.println(finally);}字节码等价于概念上try{intresult1;finallyBlock();// finally 在 return 前执行returnresult;}catch(Throwablee){finallyBlock();throwe;}关键规则finally块会在try的return之前执行但如果finally里也有return它会覆盖try的返回值。3.2 返回值保存机制try { return x; } │ ▼ 先把 x 的值保存到返回值槽位局部变量表的一个 slot │ ▼ 执行 finally 块 │ ├── finally 没有 return → 返回返回值槽位里的值 └── finally 有 return y → 返回 y覆盖3.3 异常被吞的字节码原理finally块里的return会导致方法正常返回JVM 不会再向外抛异常。try { throw new RuntimeException(); } │ ▼ 执行 finally 块 │ ├── finally 没有 return → 异常继续向外抛 ✅ └── finally 有 return → 方法正常返回异常被丢弃 ❌四、正确写法4.1 finally 里不要写 return// ❌ 错误publicstaticStringtest(){try{returntry;}finally{returnfinally;// 禁止}}// ✅ 正确finally 只做清理不返回值publicstaticStringtest(){try{returntry;}finally{System.out.println(清理资源);// 没有 return}}4.2 用 try-with-resourcesJava 7// ✅ 推荐自动关闭资源不需要手动写 finallytry(FileInputStreamfisnewFileInputStream(file.txt);BufferedReaderbrnewBufferedReader(newInputStreamReader(fis))){returnbr.readLine();}catch(IOExceptione){log.error(读取文件失败,e);returnnull;}// 不需要 finally资源自动关闭4.3 finally 里只做资源释放不写业务逻辑// ✅ 正确写法publicvoidprocess(Longid){LocklocklockService.getLock(id);lock.lock();try{// 业务逻辑doProcess(id);}finally{// 只做资源释放lock.unlock();}}4.4 如果 finally 里可能抛异常要单独 try-catch// ✅ 正确保证资源清理的异常不影响主流程的异常publicvoidprocess(){LocklocklockService.getLock();lock.lock();try{doProcess();}finally{try{lock.unlock();// unlock 也可能抛异常}catch(Exceptione){log.warn(释放锁失败,e);}}}五、最佳实践✅ finally 块的 3 条铁律finally 里永远不要写returnIDEA 会直接报警告finally 里只做资源释放不写业务逻辑finally 里抛出的异常会覆盖 try 里的异常要尽量处理掉 字节码验证返回值究竟怎么保存的可以用javap -c查看编译后的字节码javac Test.java javap-cTest.class你会看到astore/iload等指令在finally之前保存返回值。️ IDEA inspections 开启finally block does not complete normally→ 警告finally 块里有 return/throwreturn inside finally block→错误finally 里的 return六、小结finally在try的return之前执行finally里有return→ 覆盖try的返回值finally里有return→吞掉try里抛出的异常最危险finally 块的正确用途只有一种释放资源Java 7 优先用try-with-resources避免手写 finallyIDEA 里 finally 写 return 会标黄千万不要忽略这个警告下一篇预告泛型擦除为什么ListString和ListInteger是一家人—— 泛型只在编译期存在运行期被擦除了这个设计带来了哪些坑