Skip to content
清晨的一缕阳光
返回

Java 异常处理最佳实践

Java 异常处理最佳实践

异常处理是 Java 编程的重要部分,良好的异常处理能提高代码健壮性和可维护性。

一、异常体系

1.1 异常层次结构

graph TB
    A[Throwable] --> B[Error]
    A --> C[Exception]
    B --> B1[OutOfMemoryError]
    B --> B2[StackOverflowError]
    B --> B3[VirtualMachineError]
    C --> C1[RuntimeException]
    C --> C2[Checked Exception]
    C1 --> C11[NullPointerException]
    C1 --> C12[IllegalArgumentException]
    C1 --> C13[IndexOutOfBoundsException]
    C2 --> C21[IOException]
    C2 --> C22[SQLException]
    C2 --> C23[ClassNotFoundException]

1.2 Error vs Exception

特性ErrorException
含义JVM 错误,无法恢复程序错误,可处理
例子OOM、StackOverflowIOException、SQLException
处理不应捕获应捕获处理
恢复无法恢复可恢复

1.3 Checked vs Unchecked

// Checked Exception(必须处理)
try {
    FileInputStream fis = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {
    // 必须捕获或声明抛出
}

// Unchecked Exception(运行时异常)
List<String> list = null;
list.size(); // NullPointerException,可不处理

常见 Checked 异常

常见 Unchecked 异常


二、异常处理机制

2.1 try-catch-finally

try {
    // 可能抛出异常的代码
    FileInputStream fis = new FileInputStream("file.txt");
    fis.read();
} catch (FileNotFoundException e) {
    // 处理特定异常
    e.printStackTrace();
} catch (IOException e) {
    // 处理其他 IO 异常
    e.printStackTrace();
} finally {
    // 总是执行(清理资源)
    System.out.println("Cleanup");
}

2.2 try-with-resources(JDK 7+)

// 自动关闭资源
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    
    String line = br.readLine();
    
} catch (IOException e) {
    e.printStackTrace();
}
// 自动关闭,无需 finally

// 要求:资源实现 AutoCloseable 接口

2.3 多重捕获(JDK 7+)

// 之前
try {
    // ...
} catch (IOException e) {
    handle(e);
} catch (SQLException e) {
    handle(e);
}

// JDK 7+
try {
    // ...
} catch (IOException | SQLException e) {
    handle(e); // e 是 final 的
}

2.4 异常重新抛出(JDK 7+)

public void process() throws IOException, SQLException {
    try {
        // ...
    } catch (Exception e) {
        // 编译器能推断具体类型
        throw e;
    }
}

三、throw vs throws

3.1 throw(抛出异常)

// 手动抛出异常
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Invalid age: " + age);
    }
    this.age = age;
}

3.2 throws(声明异常)

// 声明可能抛出的异常
public void readFile(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    fis.read();
    fis.close();
}

// 调用者必须处理
try {
    readFile("file.txt");
} catch (IOException e) {
    e.printStackTrace();
}

四、自定义异常

4.1 创建自定义异常

// Checked 异常
public class BusinessException extends Exception {
    private String code;
    
    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
    
    public String getCode() {
        return code;
    }
}

// Unchecked 异常
public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }
}

4.2 使用自定义异常

public class UserService {
    public void register(String username) throws BusinessException {
        if (username == null || username.isEmpty()) {
            throw new BusinessException("USER_001", "用户名不能为空");
        }
        // ...
    }
}

// 调用
try {
    userService.register("");
} catch (BusinessException e) {
    System.out.println("Error " + e.getCode() + ": " + e.getMessage());
}

五、异常链

5.1 保留原始异常

// ❌ 丢失原始异常
public void process() {
    try {
        // ...
    } catch (SQLException e) {
        throw new RuntimeException("Database error"); // 丢失原始异常
    }
}

// ✅ 保留原始异常
public void process() {
    try {
        // ...
    } catch (SQLException e) {
        throw new RuntimeException("Database error", e); // 保留因果
    }
}

5.2 获取原始异常

try {
    process();
} catch (RuntimeException e) {
    Throwable cause = e.getCause(); // 获取原始异常
    if (cause instanceof SQLException) {
        // 处理 SQL 异常
    }
}

六、最佳实践

6.1 捕获具体的异常

// ❌ 捕获过于宽泛
try {
    // ...
} catch (Exception e) {
    e.printStackTrace();
}

// ✅ 捕获具体异常
try {
    // ...
} catch (FileNotFoundException e) {
    log.error("File not found", e);
} catch (IOException e) {
    log.error("IO error", e);
}

6.2 不要忽略异常

// ❌ 忽略异常
try {
    // ...
} catch (IOException e) {
    // 什么都不做
}

// ✅ 处理异常
try {
    // ...
} catch (IOException e) {
    log.error("IO error", e);
    throw new RuntimeException("Failed to process", e);
}

6.3 不要吞掉异常

// ❌ 吞掉异常
public void process() {
    try {
        // ...
    } catch (Exception e) {
        e.printStackTrace(); // 只是打印,不处理
    }
}

// ✅ 向上抛出
public void process() throws Exception {
    try {
        // ...
    } catch (Exception e) {
        log.error("Error", e);
        throw e; // 或包装后抛出
    }
}

6.4 使用标准异常

// ❌ 自定义不必要的异常
public class NullValueException extends Exception {}

// ✅ 使用标准异常
if (value == null) {
    throw new IllegalArgumentException("Value cannot be null");
}

6.5 优先使用 Unchecked 异常

// ✅ 业务异常使用 RuntimeException
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

// 调用者可选择性处理
try {
    service.process();
} catch (BusinessException e) {
    // 可选处理
}

6.6 finally 中不要抛出异常

// ❌ finally 抛出异常
try {
    // ...
} finally {
    close(); // 可能抛出异常,掩盖 try 中的异常
}

// ✅ 正确处理
try {
    // ...
} finally {
    try {
        close();
    } catch (IOException e) {
        log.error("Close failed", e);
    }
}

6.7 异常日志记录

// ❌ 只记录消息
catch (Exception e) {
    log.error("Error: " + e.getMessage());
}

// ✅ 记录完整堆栈
catch (Exception e) {
    log.error("Error processing request", e);
}

七、常见误区

7.1 过度使用异常

// ❌ 异常用于控制流程
try {
    return list.get(index);
} catch (IndexOutOfBoundsException e) {
    return defaultValue;
}

// ✅ 先检查
if (index >= 0 && index < list.size()) {
    return list.get(index);
}
return defaultValue;

7.2 异常性能开销

// 异常创建开销大
// 避免在循环中频繁创建异常

// ❌ 性能差
for (int i = 0; i < 10000; i++) {
    try {
        validate(i);
    } catch (ValidationException e) {
        // ...
    }
}

// ✅ 先验证
for (int i = 0; i < 10000; i++) {
    if (!isValid(i)) {
        continue;
    }
    process(i);
}

八、总结

异常处理核心要点:

原则说明
捕获具体异常不要捕获 Exception
不要忽略异常必须处理或记录
保留异常链包装时传入原始异常
优先 Unchecked业务异常用 RuntimeException
使用 try-with-resources自动关闭资源
记录完整堆栈log.error(“msg”, e)

良好的异常处理能提高代码健壮性,遵循最佳实践让代码更优雅。


分享这篇文章到:

上一篇文章
Go 指针与内存安全
下一篇文章
Go 数据类型系统详解