想象你正在开车。有两类危险情况可能发生:

  1. 突发状况:比如一只猫突然窜到马路上(运行时异常)
  2. 可预见的危险:比如前方道路施工(受检异常)

对于突发状况,我们没法提前准备,只能在发生时立即应对。但对于道路施工这种可预见的情况,如果没有提前警示牌告诉你,那就是很危险的疏忽。

// 读取文件是一个可预见的风险操作
public void readFile(String path) throws IOException {  // 编译器强制你声明这个风险
    FileReader reader = new FileReader(path);
    // 文件操作
}
 
public void processFile() {
    // 编译器强制你处理这个风险,两种方式:
    
    // 方式1:主动处理风险
    try {
        readFile("data.txt");
    } catch (IOException e) {
        // 处理文件读取失败的情况
        logger.error("文件读取失败", e);
    }
    
    // 方式2:向上传递风险,让调用者知道并处理
    throws IOException {
        readFile("data.txt");
    }
}

为什么这么设计?主要有三个原因:

  • 提供编译时的安全保证
// 如果不处理 IOException,代码将无法通过编译
public void processFile() {
    readFile("data.txt");  // 编译错误!
}
  • 形成契约精神,即保证调用者一定要考虑这个情况
// 方法签名就是一个契约,明确告诉调用者可能的风险
public void transferMoney(Account from, Account to, double amount) throws InsufficientFundsException {
    // 转账逻辑
}
 
// 调用者必须考虑这个风险
try {
    bank.transferMoney(accountA, accountB, 1000);
} catch (InsufficientFundsException e) {
    // 处理余额不足的情况
}
  • 提高代码质量和可维护性
// 不好的设计:风险被隐藏
public boolean deletefile(String path) {
    File file = new File(path);
    return file.delete();  // 可能失败,但调用者并不知道原因
}
 
// 好的设计:风险被显式声明
public void deleteFile(String path) throws IOException {
    File file = new File(path);
    if (!file.exists()) {
        throw new FileNotFoundException("文件不存在");
    }
    if (!file.canWrite()) {
        throw new AccessDeniedException("没有删除权限");
    }
    if (!file.delete()) {
        throw new IOException("删除失败");
    }
}