Java异常处理中finally关键字的使用疑问
嘿,这个问题问得特别戳点!很多刚摸Java异常处理的同学都会有这个疑惑——既然catch之后的代码照样能跑,那finally岂不是多此一举?其实不然,咱们得看try或者catch块里有没有提前打断程序正常流程的操作,这时候finally的“兜底”作用就立马显现出来了。
先看你给的两个代码块,在当前这个场景下,确实看起来效果完全一样——不管文件能不能打开,那行打印语句都会执行。但这是因为你的try和catch块里都没有任何会让流程“跳走”的操作,比如return、抛出新异常、break之类的。一旦有这些情况,普通代码和finally代码的区别就天差地别了。
给你举几个必须用finally的场景:
场景1:try/catch块里有return语句
比如咱们写个带return的方法:public static int testReturn() { try { System.out.println("执行try块代码"); return 1; // 这里直接return,后面的普通代码根本到不了 } catch (Exception e) { e.printStackTrace(); } System.out.println("catch之后的普通代码"); return 0; }调用这个方法,只会打印“执行try块代码”,然后直接return 1,后面的普通打印代码完全不会执行。但如果把打印语句移到finally里:
public static int testReturn() { try { System.out.println("执行try块代码"); return 1; } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("finally里的代码"); } return 0; }这时候调用方法,会先打印“执行try块代码”,接着执行finally里的打印,最后才return 1。哪怕try里直接return,finally的代码也一定会在return前执行。
场景2:catch块里抛出了新的异常
再比如catch块里处理完原异常后,又抛出了新的异常:public static void testNewException() { try { new FileReader("file.txt"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("触发新异常"); // 这里抛新异常,流程直接跳走 } System.out.println("catch之后的普通代码"); // 这行永远不会被执行 }但如果把打印语句放finally里:
public static void testNewException() { try { new FileReader("file.txt"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("触发新异常"); } finally { System.out.println("finally里的代码"); // 这行依然会稳稳执行 } }哪怕catch里抛出了新异常,finally的代码还是会被执行,不会被跳过。
场景3:循环里的try/catch有break/continue
要是在循环里的try块里用了break或者continue:for (int i = 0; i < 3; i++) { try { System.out.println("循环第" + i + "次"); if (i == 1) { break; // 这里直接跳出循环 } } catch (Exception e) { e.printStackTrace(); } System.out.println("循环后的普通代码"); }当i=1的时候,break会直接跳出循环,后面的普通打印代码根本不会执行。但如果把打印放finally里:
for (int i = 0; i < 3; i++) { try { System.out.println("循环第" + i + "次"); if (i == 1) { break; } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("finally里的代码"); } }哪怕触发了break跳出循环,finally里的代码还是会完整执行一次。
而finally最常用的真实场景,其实是资源释放——比如打开的文件流、数据库连接、网络连接这些,不管操作成功还是失败,有没有异常,都必须手动关闭,否则会造成资源泄漏。这时候把close()方法放在finally里,就能确保它一定会被执行:
FileReader f = null; try { f = new FileReader("file.txt"); // 读取文件的业务操作 } catch (IOException e) { e.printStackTrace(); } finally { if (f != null) { try { f.close(); // 不管流程怎么跳,这里一定会执行,保证流被关闭 } catch (IOException e) { e.printStackTrace(); } } }
要是把close()放在catch之后的普通代码里,万一try里有return或者抛出了其他未捕获的异常,那close()就永远不会被执行,这个文件流就会一直占用系统资源。
总结一下:finally的核心使命就是保证某段代码一定会被执行,不管try/catch块里有没有出现return、抛出异常、break/continue这些打断正常流程的操作。而普通代码只有在程序流程正常走到它那里的时候,才会被执行。
备注:内容来源于stack exchange,提问作者Cường

