Skip to content
第 16 章 后端 ⏱ 10 分钟阅读

第 16 章:异常处理基础 ​

学习目标 ​

  • 理解异常体系(Throwable / Exception / RuntimeException)
  • 掌握 try-catch-finally 和 try-with-resources
  • 避开异常的常见坑

一、为什么需要异常? ​

异常 = 程序运行时的错误处理机制。把"正常逻辑"和"错误处理"分开。

java
// ❌ 用返回值表示错误(容易忽略)
public int divide(int a, int b) {
    if (b == 0) return -1;   // -1 是错误码?还是合法结果?
    return a / b;
}

// ✅ 用异常(强制处理,信息完整)
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("除数不能为 0");
    return a / b;
}

二、异常体系(够用版) ​

Throwable
├─ Error:系统级错误(OOM / StackOverflow),不要捕获
└─ Exception
   ├─ RuntimeException:非受检异常(空指针、越界、除零)
   └─ 其他 Exception:受检异常(IOException、SQLException)
类型代表编译器强制?建议
受检异常(Checked)IOException✅ 强制 try 或 throws能恢复就 catch
非受检异常(Unchecked)NullPointerException❌ 不强制别 catch 掩盖,改代码
java
// 受检:必须处理
public void readFile() throws IOException {
    String content = Files.readString(Path.of("a.txt"));
}

// 非受检:不强制
public void parse(String s) {
    Integer.parseInt(s);   // 可能抛 NumberFormatException
}

⚠️ 坑 1:不要 catch RuntimeException(空指针、越界等)——这些是 bug,应该改代码,而不是 catch 后吞掉。

三、try-catch-finally ​

java
try {
    // ① 可能出错的代码
    int result = Integer.parseInt("abc");
} catch (NumberFormatException e) {
    // ② 捕获具体异常
    System.out.println("格式错误:" + e.getMessage());
} catch (Exception e) {
    // ③ 兜底(必须放最后,匹配范围从小到大)
    e.printStackTrace();
} finally {
    // ④ 无论如何都执行(清理资源)
    System.out.println("清理");
}

⚠️ 坑 2:catch 顺序必须从小到大,先子类后父类,否则编译报错。

java
catch (Exception e) { }        // ❌ 写前面会编译报错
catch (NumberFormatException e) { }   // 子类必须放前面

⚠️ 坑 3:finally 里写 return 会吞掉异常,业务代码绝对禁止:

java
try { throw new RuntimeException("业务错误"); }
finally { return; }   // ❌ 异常被吞,调用方根本不知道出错

四、try-with-resources(JDK 7+,推荐) ​

自动关闭实现了 AutoCloseable 接口的资源(如文件流、数据库连接):

java
// ❌ 传统写法:容易忘记关闭
BufferedReader reader = null;
try {
    reader = Files.newBufferedReader(Path.of("a.txt"));
    return reader.readLine();
} finally {
    if (reader != null) reader.close();   // 啰嗦
}

// ✅ try-with-resources:自动关闭
try (BufferedReader reader = Files.newBufferedReader(Path.of("a.txt"))) {
    return reader.readLine();
}   // 自动调用 reader.close()

多个资源:

java
try (
    Connection conn = dataSource.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    ResultSet rs = ps.executeQuery()
) {
    // 使用 conn / ps / rs
}   // 自动按相反顺序关闭

五、抛异常的规则 ​

什么时候 throw? ​

  • 参数校验失败 → throw new IllegalArgumentException(...)
  • 不支持的操作 → throw new UnsupportedOperationException(...)
  • 自定义业务异常(下章讲) → throw new BusinessException(...)

什么时候 throws? ​

  • 自己是"受害者",不能处理 → 往上抛
  • 例如:DAO 层读数据库抛 SQLException,Service 层可以包装后继续抛
java
public void readFile() throws IOException {   // 声明抛出
    Files.readString(Path.of("a.txt"));
}

public void processFile() {                    // 不 try,继续上抛
    readFile();   // 调用方也得处理或继续抛
}

六、本章小结 ​

要点关键
ThrowableError(系统级,不捕获)+ Exception
受检异常编译期强制处理,代表外部环境问题
非受检异常RuntimeException,代表程序 bug,不要 catch
try-catch-finallycatch 顺序:子类→父类,finally 不写 return
try-with-resources自动关闭资源,JDK 7+ 强烈推荐
何时抛/接不能处理就抛,能处理就接

动手练习 ​

  1. 除零异常:写 divide(int a, int b),除零时抛 IllegalArgumentException
  2. 读取文件:用 try-with-resources 读文件,捕获 IOException
  3. 异常链:catch 一个异常后包装成业务异常再抛出,带上 cause

下一章:第 17 章:自定义异常与最佳实践 →

本站基于 VitePress 构建 · 由 StackHub 团队维护