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);总结
- 区分异常类型:受检 vs 非受检
- 合理处理:捕获能处理的,抛出不能处理的
- 不要滥用:异常不是控制流工具
- 记录信息:保留异常堆栈信息
- 自定义异常:业务异常要清晰明了