基本语法
try块里放”可能出事”的代码,catch块里写”出事了怎么办”
就像你过马路(try),万一被车撞了(catch),送医院处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testBasicTryCatch() { System.out.println("=== 基本 try-catch ===");
try { int result = 10 / 0; System.out.println("这行不会执行"); } catch (ArithmeticException e) { System.out.println("捕获到异常:" + e.getMessage()); } System.out.println("程序继续执行,不会崩"); }
|
关键点:try块中一旦某行抛了异常,这行后面的代码不会执行,直接跳到catch块
多个catch
一个try可以配多个catch,就像医院有不同科室,什么病去什么科
规则:从具体到笼统排列,子类异常在前,父类异常在后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test public void testMultiCatch() { System.out.println("=== 多个 catch ===");
try { String str = null; System.out.println(str.length()); } catch (NullPointerException e) { System.out.println("空指针异常:" + e.getMessage()); } catch (RuntimeException e) { System.out.println("运行时异常:" + e.getMessage()); } catch (Exception e) { System.out.println("其他异常:" + e.getMessage()); } }
|
为什么要从具体到笼统?
如果把 Exception 放第一个,后面的catch永远不会被执行到,编译器直接报错
就像你挂号先说”我浑身不舒服”,那导诊台没法给你分科室
Java 7+ 多异常合并写法
1 2 3 4 5 6
| try {
} catch (NullPointerException | ArithmeticException e) { System.out.println("出错了:" + e.getMessage()); }
|
finally
无论是否发生异常,finally块都会执行
就像你不管考试考得好不好,回家都得吃饭
最常见的用途:关闭资源(文件流、数据库连接、网络连接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void testFinally() { System.out.println("=== finally ===");
FileInputStream fis = null; try { fis = new FileInputStream("test.txt"); System.out.println("读文件成功"); } catch (FileNotFoundException e) { System.out.println("文件不存在:" + e.getMessage()); } finally { System.out.println("finally执行了!"); if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
你看上面的代码,光关一个文件流就写了一堆,又丑又长,所以Java 7引入了 try-catch-finally#try-with-resources
try-with-resources(Java 7+)
自动关闭资源,不用写finally了,代码简洁得多
条件:资源类必须实现 AutoCloseable 接口(大部分IO类都实现了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Test public void testTryWithResources() { System.out.println("=== try-with-resources ===");
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { String line = br.readLine(); System.out.println("读到:" + line); } catch (IOException e) { System.out.println("读取失败:" + e.getMessage()); }
try ( FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt") ) { } catch (IOException e) { e.printStackTrace(); } }
|
实战建议:只要是Java 7+的项目,永远用 try-with-resources 代替 try-finally 来关资源
finally中return的坑
这是经典面试题,也是实际开发中的大坑
finally中的return会覆盖try/catch中的return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void testFinallyReturn() { System.out.println("=== finally中的return ==="); System.out.println("结果:" + getValue()); }
public int getValue() { try { return 10; } catch (Exception e) { return 20; } finally { return 30; } }
|
结论:永远不要在finally中写return! 这会让代码行为变得诡异,IDE也会给你警告
更阴险的例子:finally修改返回值
1 2 3 4 5 6 7 8 9 10
| public int trickQuestion() { int x = 1; try { return x; } finally { x = 2; }
}
|
执行顺序详解
情况1:没有异常
try → finally → 方法返回
1 2 3 4 5 6 7 8 9 10 11 12
| @Test public void testNoException() { try { System.out.println("1. try块执行"); } catch (Exception e) { System.out.println("2. catch块执行"); } finally { System.out.println("3. finally执行"); } System.out.println("4. try-catch后面的代码");
}
|
情况2:有异常,被catch接住
try(到异常那行为止)→ catch → finally → 继续往下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testWithException() { try { System.out.println("1. try块开始"); int x = 1 / 0; System.out.println("2. 这行不会执行"); } catch (ArithmeticException e) { System.out.println("3. catch块执行"); } finally { System.out.println("4. finally执行"); } System.out.println("5. 继续执行");
}
|
情况3:有异常,没被catch接住
try(到异常那行为止)→ finally → 异常继续往上抛 → 后面的代码不执行
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void testUncaughtException() { try { System.out.println("1. try块开始"); int x = 1 / 0; } catch (NullPointerException e) { System.out.println("2. 类型不匹配,接不住"); } finally { System.out.println("3. finally照样执行"); } System.out.println("4. 这行不会执行");
}
|
情况4:try中有return
try(执行到return,先算好返回值存起来)→ finally → 返回之前存好的值
| 场景 |
执行顺序 |
| 无异常 |
try → finally → 后续代码 |
| 有异常,catch匹配 |
try(部分) → catch → finally → 后续代码 |
| 有异常,catch不匹配 |
try(部分) → finally → 异常继续上抛 |
| try中有return |
try → finally → 返回try中的值 |
| finally中有return |
try → finally → 返回finally中的值(覆盖!) |
常见坑
坑1:catch块里啥也不写(吞异常)
1 2 3 4 5 6 7 8 9 10 11 12 13
| try { doSomething(); } catch (Exception e) {
}
try { doSomething(); } catch (Exception e) { log.error("操作失败", e); }
|
坑2:catch范围太大
别动不动就 catch (Exception e),应该catch你预期会出现的具体异常
太大的catch会掩盖你没预料到的bug
坑3:try块包太多代码
try块应该只包住可能出异常的那几行,不要把整个方法体都扔进去
范围越小,越容易定位问题
坑4:在finally里抛异常
finally里如果抛了新异常,会覆盖try/catch里的原始异常,原始异常就丢了
练习题
题1:说出下面代码的输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static int test() { try { System.out.println("try"); return 1; } catch (Exception e) { System.out.println("catch"); return 2; } finally { System.out.println("finally"); } }
|
题2:下面的代码返回值是多少?
1 2 3 4 5 6 7 8 9 10
| public static int test2() { int x = 0; try { x = 1; return x; } finally { x = 2; } }
|
题3:用 try-with-resources 改写以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| BufferedReader br = null; try { br = new BufferedReader(new FileReader("data.txt")); String line = br.readLine(); System.out.println(line); } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } }
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { String line = br.readLine(); System.out.println(line); } catch (IOException e) { e.printStackTrace(); }
|
相关笔记
异常体系 | throw与throws | 自定义异常