checked vs unchecked

Java 独有两类异常:

派生自 必须 catch 或 throws 例子
checked Exception(非 RuntimeException) IOException、SQLException
unchecked RuntimeException NullPointerException、IllegalArgument、IndexOutOfBounds
Error Error 不该 catch OutOfMemoryError、StackOverflowError

抛异常

public void withdraw(double amount) {
    if (amount <= 0)
        throw new IllegalArgumentException("金额必须 > 0");

    if (amount > balance)
        throw new InsufficientFundsException(balance, amount);

    balance -= amount;
}

IllegalArgumentException 是 unchecked——不需要 throws 声明。

受检异常的传播

public String read() throws IOException {       // 必须声明
    return Files.readString(path);
}

// 调用方处理
try {
    var s = read();
} catch (IOException e) {
    ...
}

// 或继续向上传
public String load() throws IOException {       // 转抛
    return read();
}

try / catch / finally

try {
    doRisky();
} catch (FileNotFoundException e) {
    System.err.println("没找到: " + e.getMessage());
} catch (IOException e) {                       // 父异常
    System.err.println("IO 错: " + e);
} catch (Exception e) {
    System.err.println("其他: " + e);
} finally {
    cleanup();         // 无论成功失败都执行
}

catch 顺序从具体到一般——子异常在前。

多类型 catch

try {
    ...
} catch (IOException | SQLException e) {       // | 分隔
    // e 类型是 IOException 和 SQLException 的共同父类
    handle(e);
}

try-with-resources

try (var reader = Files.newBufferedReader(path);
     var writer = Files.newBufferedWriter(out)) {
    // 用 reader / writer
    reader.lines().forEach(writer::println);
}   // 自动 close(哪怕中间抛异常)

任何 AutoCloseable 都能放。取代 Java 6 时代的 finally { try { resource.close(); } ... }

异常链

try {
    doSomething();
} catch (SQLException e) {
    throw new RepositoryException("查询失败", e);    // 原因放第二个参数
}

调用方拿到 RepositoryException + getCause() 拿原 SQLException——堆栈链完整。

重新抛

try {
    doSomething();
} catch (Exception e) {
    log.error("失败", e);
    throw e;                                    // ✅ 保留原异常
    // throw new Exception(e);                    // 包装
}

throw e; 直接重抛——堆栈不变。

自定义异常

public class InsufficientFundsException extends RuntimeException {
    private final double available;
    private final double requested;

    public InsufficientFundsException(double available, double requested) {
        super("余额 " + available + " 不足扣 " + requested);
        this.available = available;
        this.requested = requested;
    }

    public double getAvailable() { return available; }
    public double getRequested() { return requested; }
}

约定:

  • 继承 RuntimeException首选——除非真的需要强制调用方处理)
  • 名字以 Exception 结尾
  • 提供 (String) / (String, Throwable) 构造

checked 异常的争议

Java 独有,社区争议大:

支持:编译器强制处理 → 不会漏。 反对

  • 让 lambda / Stream / Optional 等函数式 API 别扭
  • 强迫 throws 链一路声明
  • 容易导致"catch 后吞掉"反模式

现代 Java 风格:业务异常用 RuntimeException 子类。受检异常只在真"必须处理"的边界(如 IO)。

Spring / Kotlin / 大多数现代 Java 框架都基本不抛受检异常。

不要吞异常

// ❌ 灾难
try {
    doSomething();
} catch (Exception e) {
    // 静默忽略
}

// ✅ 至少日志
try {
    doSomething();
} catch (Exception e) {
    log.error("失败", e);
    throw new RuntimeException(e);   // 或继续抛
}

最差的代码模式之一——出问题永远不知道原因。

try-finally vs try-with-resources

// 老
var f = new FileInputStream(path);
try {
    process(f);
} finally {
    f.close();        // 但 close 也可能抛异常 → 主异常被覆盖
}

// 新
try (var f = new FileInputStream(path)) {
    process(f);
}   // 自动 close + 抑制异常(getSuppressed 拿)

新代码全 try-with-resources。

catch 之后的判断

try {
    fetch(url);
} catch (HttpException e) {
    if (e.getStatusCode() == 404) {
        // 处理 404
    } else {
        throw e;          // 不处理就重抛
    }
}

或用 Java 8+ 简化模式:

catch (HttpException e) when (...) {     // ❌ Java 没有,C# 有
}

Java 没 catch when——只能 catch 完手动 if。

不该 catch 的异常

// 通常是 bug,让程序崩溃修
NullPointerException
IllegalArgumentException
IllegalStateException
IndexOutOfBoundsException
ClassCastException
ArrayStoreException

// 系统级,无法处理
OutOfMemoryError
StackOverflowError
ThreadDeath

catch 应该是恢复 / 兜底——bug 让它崩,崩了就修。

全局兜底

Thread.setDefaultUncaughtExceptionHandler((thread, e) -> {
    log.error("未处理异常 in {}", thread, e);
});

最后一线日志。Spring Boot 等框架自动配。

性能

异常开销大——fillInStackTrace 抓堆栈是慢操作。不要用异常做控制流

// ❌ 慢
try {
    int n = Integer.parseInt(s);
    use(n);
} catch (NumberFormatException e) {
    use(0);
}

// ✅ 快——预检
if (s.chars().allMatch(Character::isDigit)) {
    use(Integer.parseInt(s));
} else {
    use(0);
}

// 或在 Java 里没 TryParse,但有 OptionalInt

实战清单

  • 业务异常 → 继承 RuntimeException
  • 资源管理 → try-with-resources
  • 不要 catch 不能处理的异常(让它向上传)
  • 永远不要吞异常(至少日志)
  • 不要用异常做控制流(用 Optional / TryXxx 模式)

→ 下一篇 Java 路线图