第46章

Java自定义异常

掌握自定义异常类的创建和使用,理解异常继承体系与最佳实践

学习目标

自定义异常类型详解

在Java编程中,虽然标准库提供了丰富的异常类型,但在实际开发中,我们经常需要创建自定义异常来更准确地表达特定的错误情况。自定义异常能够提供更清晰的错误信息,便于问题定位和处理。

检查型异常(Checked Exception)

继承Exception类:
public class InvalidAgeException extends Exception {
    private String errorCode;
    
    public InvalidAgeException(String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public String getErrorCode() {
        return errorCode;
    }
}
  • 必须在方法签名中声明(throws)
  • 必须被捕获处理或继续抛出
  • 用于可预期的异常情况
  • 强制调用者处理异常

非检查型异常(Unchecked Exception)

继承RuntimeException类:
public class InsufficientFundsException extends RuntimeException {
    private double currentBalance;
    private double requestedAmount;
    
    public InsufficientFundsException(String message, 
            double currentBalance, double requestedAmount) {
        super(message);
        this.currentBalance = currentBalance;
        this.requestedAmount = requestedAmount;
    }
}
  • 不需要在方法签名中声明
  • 可以选择性捕获处理
  • 用于编程错误或运行时错误
  • 不强制调用者处理

异常继承体系设计

良好的异常继承体系能够提供清晰的异常分类,便于异常处理和维护。设计时应该根据业务领域和错误类型进行合理分层。

异常继承层次示例:
// 基础业务异常
abstract class BusinessLogicException extends Exception {
    private String errorCode;
    
    public BusinessLogicException(String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public String getErrorCode() {
        return errorCode;
    }
}

// 订单相关异常
class InvalidOrderException extends BusinessLogicException {
    public InvalidOrderException(String message, String errorCode) {
        super(message, errorCode);
    }
}

// 用户相关异常
class UserRegistrationException extends BusinessLogicException {
    private String failureReason;
    
    public UserRegistrationException(String message, String failureReason) {
        super(message, "USER_REGISTRATION_FAILED");
        this.failureReason = failureReason;
    }
}
异常层级 说明 使用场景
基础异常类 定义通用的异常属性和方法 作为其他异常的父类,提供统一接口
领域异常类 按业务领域分类的异常 数据库异常、网络异常、业务异常等
具体异常类 特定错误情况的异常 用户不存在、余额不足、文件未找到等

异常链和异常包装

异常链是一种重要的异常处理技术,它允许我们在抛出新异常时保留原始异常的信息,这对于问题诊断和调试非常有用。

异常链示例:
public class ServiceException extends Exception {
    public ServiceException(String message) {
        super(message);
    }
    
    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }
}

public void businessMethod() throws ServiceException {
    try {
        // 调用底层方法
        databaseOperation();
    } catch (SQLException e) {
        // 包装为业务异常,保留原始异常
        throw new ServiceException("用户服务操作失败", e);
    }
}

异常链的优势

  • 保留完整信息:不丢失原始异常的堆栈跟踪
  • 层次清晰:将底层技术异常转换为业务异常
  • 便于调试:可以追踪异常的完整传播路径
  • 封装实现:隐藏底层实现细节

完整代码示例

以下是一个完整的自定义异常使用示例,展示了如何在实际项目中设计和使用自定义异常。

CustomExceptionExample.java - 完整示例
/**
 * 自定义异常示例程序
 * 演示如何创建和使用自定义异常类
 */
public class CustomExceptionExample {
    
    public static void main(String[] args) {
        CustomExceptionExample example = new CustomExceptionExample();
        
        try {
            // 测试年龄验证
            example.validateAge(15);
        } catch (InvalidAgeException e) {
            System.out.println("捕获到自定义异常: " + e.getMessage());
            System.out.println("错误代码: " + e.getErrorCode());
        }
        
        try {
            // 测试银行账户操作
            BankAccount account = new BankAccount("123456", 1000.0);
            account.withdraw(1500.0);
        } catch (InsufficientFundsException e) {
            System.out.println("银行操作异常: " + e.getMessage());
            System.out.println("当前余额: " + e.getCurrentBalance());
            System.out.println("尝试提取: " + e.getRequestedAmount());
        }
    }
    
    /**
     * 验证年龄
     * 
     * @param age 年龄
     * @throws InvalidAgeException 当年龄无效时抛出
     */
    public void validateAge(int age) throws InvalidAgeException {
        if (age < 0) {
            throw new InvalidAgeException("年龄不能为负数", "AGE_NEGATIVE");
        }
        if (age > 150) {
            throw new InvalidAgeException("年龄不能超过150岁", "AGE_TOO_HIGH");
        }
        if (age < 18) {
            throw new InvalidAgeException("年龄必须满18岁", "AGE_TOO_LOW");
        }
        System.out.println("年龄验证通过: " + age + "岁");
    }
}
💻 查看完整代码 - 在线IDE体验

异常设计最佳实践

异常设计原则

推荐做法

// 明确的异常命名
InvalidEmailFormatException
DatabaseConnectionTimeoutException
InsufficientPermissionException

// 提供有用的错误信息
throw new InvalidAgeException(
    String.format("年龄%d超出有效范围[0-150]", age),
    "AGE_OUT_OF_RANGE"
);

// 保留异常链
try {
    lowLevelOperation();
} catch (IOException e) {
    throw new ServiceException("服务操作失败", e);
}
  • 使用清晰描述性的异常名称
  • 提供详细的错误信息和上下文
  • 保留原始异常信息(异常链)
  • 包含足够信息供调用者处理

应该避免

// 模糊的异常命名
MyException
BadException
ErrorException

// 丢失原始异常信息
try {
    operation();
} catch (Exception e) {
    throw new CustomException("操作失败"); // 丢失了原始异常
}

// 过度使用检查型异常
public void setAge(int age) throws InvalidAgeException // 过度
  • 使用模糊或无意义的异常名称
  • 丢失原始异常的堆栈信息
  • 过度使用检查型异常
  • 异常信息不够具体或有用

异常处理策略

选择异常类型的指导原则

  • 检查型异常:用于可预期且调用者应该处理的错误情况
  • 非检查型异常:用于编程错误或不可恢复的运行时错误
  • 异常转换:将底层技术异常转换为业务异常
  • 快速失败:在发现错误时立即抛出异常

异常设计注意事项

  • 避免创建过多的异常类,保持合理的粒度
  • 异常类应该是不可变的,避免状态变化
  • 考虑异常的性能影响,避免在热点代码中频繁抛出
  • 为异常提供国际化支持,便于多语言环境
  • 文档化异常的使用场景和处理建议

本章小结