目录

Java 异常处理机制详解

系统梳理 Java 异常体系结构、处理机制与最佳实践

异常概述

异常(Exception)是程序运行过程中发生的不正常事件,中断了程序的正常执行流程。

异常体系结构

Throwable
├── Error(严重错误,不应捕获)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception(程序可处理)
    ├── RuntimeException(运行时异常)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ...
    └── 其他异常(受检异常)
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── ...

异常分类

1. 受检异常(Checked Exception)

  • 编译时检查
  • 必须处理或声明抛出
  • 继承自 Exception 但不是 RuntimeException
// 必须处理
public void readFile() throws IOException {
    FileReader reader = new FileReader("file.txt");
}

2. 非受检异常(Unchecked Exception)

  • 运行时检查
  • 不强制处理
  • 继承自 RuntimeException
// 不强制处理,但应该避免
public void divide(int a, int b) {
    int result = a / b;  // 可能抛出 ArithmeticException
}

异常处理

try-catch

try {
    // 可能抛出异常的代码
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 处理异常
    System.out.println("除数不能为0");
}

多个 catch

try {
    // 可能抛出多种异常
    int[] arr = new int[5];
    arr[10] = 100;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界");
} catch (Exception e) {
    System.out.println("其他异常");
}

注意:子类异常在前,父类异常在后。

try-catch-finally

FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // 读取文件
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 一定会执行
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try-with-resources(Java 7+)

// 自动关闭资源
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// 自动调用 close()

抛出异常

throw

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年龄不能为负数");
    }
    this.age = age;
}

throws

// 声明方法可能抛出的异常
public void readFile(String path) throws FileNotFoundException, IOException {
    FileReader reader = new FileReader(path);
    reader.read();
}

自定义异常

// 自定义受检异常
public class BusinessException extends Exception {
    private String errorCode;

    public BusinessException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

// 自定义运行时异常
public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }

    public ValidationException(String message, Throwable cause) {
        super(message, cause);
    }
}

使用自定义异常:

public void transfer(Account from, Account to, BigDecimal amount) 
        throws BusinessException {
    if (from.getBalance().compareTo(amount) < 0) {
        throw new BusinessException("E001", "余额不足");
    }
    // 转账逻辑
}

异常链

try {
    readFile();
} catch (IOException e) {
    // 包装异常,保留原始信息
    throw new ServiceException("文件读取失败", e);
}

最佳实践

1. 不要捕获 Throwable

// 错误
try {
    // ...
} catch (Throwable t) {  // 不要这样做!
    // ...
}

// 正确
try {
    // ...
} catch (SpecificException e) {
    // ...
}

2. 不要忽略异常

// 错误
try {
    doSomething();
} catch (Exception e) {
    // 空的 catch 块!
}

// 正确
try {
    doSomething();
} catch (Exception e) {
    logger.error("操作失败", e);
    // 或抛出
    throw new RuntimeException("操作失败", e);
}

3. 早抛出,晚捕获

// 在底层抛出具体异常
public void parseInt(String str) {
    if (str == null || str.isEmpty()) {
        throw new IllegalArgumentException("字符串不能为空");
    }
    // ...
}

// 在高层统一处理
public void process() {
    try {
        parseInt(input);
        doSomethingElse();
    } catch (IllegalArgumentException e) {
        // 统一处理参数错误
        showError(e.getMessage());
    }
}

4. 使用标准异常

场景异常类型
参数错误IllegalArgumentException
空指针NullPointerException
索引越界IndexOutOfBoundsException
状态错误IllegalStateException
不支持操作UnsupportedOperationException
找不到元素NoSuchElementException

5. 异常信息要清晰

// 不好
throw new IllegalArgumentException("error");

// 好
throw new IllegalArgumentException("用户ID不能为空");

// 更好
throw new IllegalArgumentException("用户ID不能为空,当前值: " + userId);

Java 8 Optional 处理空值

// 避免 NullPointerException
Optional<String> optional = Optional.ofNullable(getString());

// 取值
String value = optional.orElse("default");
String value2 = optional.orElseThrow(() -> new NotFoundException("未找到"));

// 链式操作
optional.filter(s -> s.length() > 0)
        .map(String::toUpperCase)
        .ifPresent(System.out::println);

总结

  1. 区分异常类型:受检 vs 非受检
  2. 合理处理:捕获能处理的,抛出不能处理的
  3. 不要滥用:异常不是控制流工具
  4. 记录信息:保留异常堆栈信息
  5. 自定义异常:业务异常要清晰明了