引言:为什么异常处理如此重要?
想象一下,你正在开发一个银行转账系统。如果没有恰当的异常处理,一次网络中断或数据库故障就可能导致资金损失或数据不一致。Java的异常处理机制正是为了优雅地处理这些"意外情况",确保程序的健壮性和可靠性。
异常的分类体系
Java异常的层次结构
java
Throwable (所有错误和异常的父类)
├── Error (严重错误,通常程序无法恢复)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (可处理的异常)
├── RuntimeException (运行时异常,非受检异常)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── ArithmeticException
└── 受检异常 (Checked Exception)
├── IOException
├── SQLException
└── ClassNotFoundException
基础异常处理:try-catch-finally
基本语法结构
java
public class BasicExceptionHandling {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("请输入一个数字: ");
int number = Integer.parseInt(scanner.nextLine());
System.out.println("你输入的数字是: " + number);
} catch (NumberFormatException e) {
System.out.println("错误:请输入有效的数字!");
System.out.println("详细信息: " + e.getMessage());
} catch (Exception e) {
System.out.println("发生了未知错误: " + e.getClass().getName());
} finally {
System.out.println("程序执行结束,清理资源...");
scanner.close();
}
}
}
多个catch块的处理顺序
java
try {
// 可能抛出多种异常的代码
int[] arr = new int[5];
arr[10] = 100; // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界错误");
} catch (RuntimeException e) {
System.out.println("运行时异常");
} catch (Exception e) {
System.out.println("通用异常");
} finally {
System.out.println("总会执行的代码");
}
受检异常 vs 非受检异常
受检异常 (Checked Exception)
必须在编译时处理,否则代码无法通过编译
java
public class CheckedExceptionExample {
// 方法声明抛出受检异常
public void readFile(String filename) throws IOException {
FileReader fileReader = new FileReader(filename);
// 读取文件操作...
fileReader.close();
}
public void processFile() {
try {
readFile("data.txt");
} catch (IOException e) {
System.out.println("文件读取失败: " + e.getMessage());
// 可以选择恢复操作或重新抛出
}
}
}
非受检异常 (Unchecked Exception)
运行时异常,编译器不强制处理
java
public class UncheckedExceptionExample {
public int divide(int a, int b) {
// 可能抛出ArithmeticException
return a / b;
}
public void processDivision() {
try {
int result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("除数不能为零!");
}
}
}
自定义异常:创建业务相关的异常
1. 创建自定义异常类
java
// 业务异常基类
public class BusinessException extends Exception {
private String errorCode;
private String errorMessage;
public BusinessException(String errorCode, String errorMessage) {
super(errorCode + ": " + errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}
// 具体的业务异常
public class InsufficientBalanceException extends BusinessException {
private double currentBalance;
private double requiredAmount;
public InsufficientBalanceException(double currentBalance, double requiredAmount) {
super("INSUFFICIENT_BALANCE",
String.format("余额不足。当前余额: %.2f, 需要金额: %.2f",
currentBalance, requiredAmount));
this.currentBalance = currentBalance;
this.requiredAmount = requiredAmount;
}
public double getCurrentBalance() {
return currentBalance;
}
public double getRequiredAmount() {
return requiredAmount;
}
}
2. 在业务逻辑中使用自定义异常
java
public class BankAccount {
private String accountNumber;
private double balance;
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
System.out.println("取款成功: " + amount);
}
public void transfer(BankAccount target, double amount)
throws InsufficientBalanceException, TransferFailedException {
try {
withdraw(amount);
target.deposit(amount);
System.out.println("转账成功");
} catch (InsufficientBalanceException e) {
throw e; // 重新抛出
} catch (Exception e) {
throw new TransferFailedException("TRANSFER_FAILED",
"转账过程中发生错误: " + e.getMessage());
}
}
}
异常处理的最佳实践
1. 不要忽略异常(反模式)
java
// ❌ 糟糕的做法:吞掉异常
try {
processSomething();
} catch (Exception e) {
// 什么都不做 - 错误被隐藏了!
}
// ✅ 好的做法:至少记录日志
try {
processSomething();
} catch (Exception e) {
log.error("处理过程中发生错误", e);
// 或者重新抛出自定义异常
throw new BusinessException("PROCESS_ERROR", "处理失败", e);
}
2. 使用try-with-resources自动管理资源
java
// Java 7之前的做法
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用流...
} catch (IOException e) {
// 处理异常
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// 关闭时的异常
}
}
}
// Java 7+的优雅做法(自动关闭实现AutoCloseable的资源)
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("文件读取失败: " + e.getMessage());
}
3. 异常链:保留原始异常信息
java
public class ExceptionChaining {
public void processData() throws BusinessException {
try {
// 可能抛出SQLException
loadFromDatabase();
} catch (SQLException e) {
// 包装原始异常,保留堆栈信息
throw new BusinessException("DATABASE_ERROR", "数据库操作失败", e);
}
}
private void loadFromDatabase() throws SQLException {
// 模拟数据库异常
throw new SQLException("连接超时");
}
}
高级异常处理模式
模式1:异常转换
java
public class DataAccessException extends RuntimeException {
public DataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
public class DataAccessor {
public List queryData() {
try {
// 调用可能抛出SQLException的方法
return executeQuery();
} catch (SQLException e) {
// 将受检异常转换为运行时异常
throw new DataAccessException("数据查询失败", e);
}
}
private List executeQuery() throws SQLException {
// 数据库查询逻辑
throw new SQLException("数据库错误");
}
}
模式2:异常处理器
java
public class GlobalExceptionHandler {
public void handleException(Exception e) {
if (e instanceof BusinessException) {
handleBusinessException((BusinessException) e);
} else if (e instanceof RuntimeException) {
handleRuntimeException((RuntimeException) e);
} else {
handleGenericException(e);
}
}
private void handleBusinessException(BusinessException e) {
System.err.println("业务异常 [" + e.getErrorCode() + "]: " + e.getMessage());
// 发送警报、记录详细日志等
}
private void handleRuntimeException(RuntimeException e) {
System.err.println("运行时异常: " + e.getClass().getName());
e.printStackTrace();
}
private void handleGenericException(Exception e) {
System.err.println("通用异常: " + e.getMessage());
logToFile(e);
}
}
模式3:防御性编程
java
public class DefensiveProgramming {
// 验证输入参数
public void processUserInput(String input) {
// 参数校验
if (input == null) {
throw new IllegalArgumentException("输入不能为null");
}
if (input.trim().isEmpty()) {
throw new IllegalArgumentException("输入不能为空");
}
// 业务逻辑
System.out.println("处理输入: " + input);
}
// 使用Optional避免NullPointerException
public Optional findUserName(int userId) {
// 模拟查找用户
if (userId == 1) {
return Optional.of("张三");
} else {
return Optional.empty(); // 而不是返回null
}
}
public void useOptional() {
Optional userName = findUserName(2);
// 安全的处理方式
userName.ifPresent(name -> System.out.println("用户: " + name));
String defaultName = userName.orElse("默认用户");
System.out.println("使用名称: " + defaultName);
}
}
实战:完整的异常处理框架
java
// 1. 异常枚举
public enum ErrorCode {
SUCCESS("0000", "成功"),
INVALID_PARAM("1001", "参数无效"),
DATABASE_ERROR("2001", "数据库错误"),
NETWORK_ERROR("3001", "网络错误"),
UNKNOWN_ERROR("9999", "未知错误");
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() { return code; }
public String getMessage() { return message; }
}
// 2. 统一响应对象
public class ApiResponse {
private String code;
private String message;
private T data;
private long timestamp;
public ApiResponse(ErrorCode errorCode, T data) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
this.data = data;
this.timestamp = System.currentTimeMillis();
}
public static ApiResponse success(T data) {
return new ApiResponse<>(ErrorCode.SUCCESS, data);
}
public static ApiResponse error(ErrorCode errorCode) {
return new ApiResponse<>(errorCode, null);
}
}
// 3. 全局异常处理器(Spring Boot示例)
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
public ResponseEntity> handleBusinessException(BusinessException e) {
logger.warn("业务异常: {}", e.getMessage(), e);
ErrorCode errorCode = ErrorCode.valueOf(e.getErrorCode());
ApiResponse response = ApiResponse.error(errorCode);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity> handleGenericException(Exception e) {
logger.error("系统异常: {}", e.getMessage(), e);
ApiResponse response = ApiResponse.error(ErrorCode.UNKNOWN_ERROR);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(response);
}
}
// 4. 服务层使用示例
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public ApiResponse getUserById(int userId) {
try {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(
ErrorCode.INVALID_PARAM.getCode(),
"用户不存在"));
return ApiResponse.success(user);
} catch (DataAccessException e) {
throw new BusinessException(
ErrorCode.DATABASE_ERROR.getCode(),
"数据库查询失败",
e);
}
}
}
性能考虑:异常处理的成本
创建异常对象的开销
java
public class ExceptionPerformance {
public static void main(String[] args) {
int iterations = 1000000;
// 测试正常返回
long start1 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
methodWithReturn();
}
long time1 = System.nanoTime() - start1;
// 测试抛出异常
long start2 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
try {
methodWithException();
} catch (Exception e) {
// 忽略
}
}
long time2 = System.nanoTime() - start2;
System.out.println("正常返回耗时: " + time1 + " ns");
System.out.println("抛出异常耗时: " + time2 + " ns");
System.out.println("异常开销倍数: " + (time2 / time1));
}
private static int methodWithReturn() {
return 42;
}
private static int methodWithException() {
throw new RuntimeException("测试异常");
}
}
总结:异常处理的核心原则
- 早抛出,晚捕获:在发现错误的地方抛出异常,在能处理的地方捕获
- 异常要具体:使用最具体的异常类型,避免捕获通用Exception
- 异常信息要丰富:提供有助于调试的错误信息和上下文
- 不要忽略异常:至少记录日志或重新抛出
- 使用受检异常要谨慎:考虑使用运行时异常替代
- 保持finally块简洁:只放清理资源的代码
- 自定义异常要有意义:反映业务错误,而不是技术错误