杰瑞科技汇

java 自定义exception

为什么需要自定义异常?

Java 提供了丰富的内置异常类,如 NullPointerException, ArrayIndexOutOfBoundsException, IOException 等,这些异常通常用于处理一些通用的、常见的错误情况。

java 自定义exception-图1
(图片来源网络,侵删)

但在实际开发中,你可能会遇到一些业务逻辑上的特定错误,这些错误无法用现有的异常类来准确描述。

  • 用户输入的身份证号格式不正确。
  • 账户余额不足以支付订单。
  • 订单状态无法从“待发货”变为“已取消”。

在这种情况下,使用自定义异常可以带来以下好处:

  1. 代码更清晰:通过异常的名称就能直观地知道错误的原因,InsufficientBalanceExceptionRuntimeException 更具描述性。
  2. 错误处理更精确:调用方可以根据不同的异常类型,采取不同的处理逻辑。InvalidInputException 可能需要提示用户重新输入,而 InsufficientBalanceException 可能需要提示用户充值。
  3. 业务逻辑与异常处理解耦:将业务逻辑的错误(如余额不足)与系统级错误(如网络中断)分离开,使代码结构更清晰。
  4. 便于调试和日志记录:在日志中记录具体的自定义异常,能更快地定位问题。

如何创建自定义异常?

创建自定义异常非常简单,只需继承 Java 中已有的异常类即可。

继承哪个类?

这是最关键的一步,通常有以下几种选择:

java 自定义exception-图2
(图片来源网络,侵删)
  • 继承 Exception

    • 这是一个受检异常(Checked Exception)
    • 它强制调用方使用 try-catch 块进行处理,或者在方法签名中使用 throws 关键字声明。
    • 适用场景:当错误情况是可以预见的,并且调用方有能力或有必要恢复时,文件不存在、网络连接超时、数据库连接失败等。
  • 继承 RuntimeException

    • 这是一个非受检异常(Unchecked Exception)
    • 调用方不是必须处理它,如果不处理,程序可能会在运行时抛出异常而终止。
    • 适用场景:通常表示编程错误或不可恢复的系统错误,空指针访问、非法的数组索引,业务逻辑上的错误,如果认为调用方不应该忽略,也可以使用 RuntimeException

总结建议

  • 对于业务逻辑错误,通常推荐继承 RuntimeException,因为它更灵活,减少了代码的繁琐性。
  • 对于系统级或外部资源相关的错误,推荐继承 Exception,强制调用方处理。

创建步骤

  1. 创建一个新类
  2. 继承 ExceptionRuntimeException
  3. (可选)提供构造方法
    • 至少提供一个无参构造方法。
    • 通常提供一个带字符串参数的构造方法,用于设置异常的描述信息。
    • (推荐)提供一个带字符串参数和 Throwable 原因的构造方法,以便于异常链的传递。

代码示例

下面我们通过一个完整的例子来演示如何创建和使用自定义异常。

java 自定义exception-图3
(图片来源网络,侵删)

场景:模拟一个简单的银行账户取款操作。

创建自定义异常类

我们将创建一个 InsufficientBalanceException(余额不足异常),它继承自 RuntimeException

// InsufficientBalanceException.java
/**
 * 自定义异常:余额不足
 * 继承 RuntimeException,这是一个非受检异常。
 */
public class InsufficientBalanceException extends RuntimeException {
    // 1. 无参构造方法
    public InsufficientBalanceException() {
        super();
    }
    // 2. 带有错误信息的构造方法
    public InsufficientBalanceException(String message) {
        super(message);
    }
    // 3. 带有错误信息和原因(另一个异常)的构造方法,用于异常链
    public InsufficientBalanceException(String message, Throwable cause) {
        super(message, cause);
    }
}

创建业务逻辑类

这个类包含一个取款方法,当余额不足时,会抛出我们刚刚定义的自定义异常。

// BankAccount.java
public class BankAccount {
    private double balance;
    public BankAccount(double initialBalance) {
        if (initialBalance < 0) {
            // 这里可以抛出一个非法参数异常
            throw new IllegalArgumentException("初始余额不能为负数");
        }
        this.balance = initialBalance;
    }
    /**
     * 取款方法
     * @param amount 要取出的金额
     * @throws InsufficientBalanceException 当余额不足时抛出
     */
    public void withdraw(double amount) {
        // 检查取款金额是否合法
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于0");
        }
        // 检查余额是否充足
        if (balance < amount) {
            // 抛出自定义异常
            throw new InsufficientBalanceException("取款失败:账户余额不足,当前余额: " + balance + ", 尝试取款: " + amount);
        }
        // 如果一切正常,执行取款操作
        balance -= amount;
        System.out.println("取款成功!当前余额: " + balance);
    }
    public double getBalance() {
        return balance;
    }
}

创建测试类来调用和捕获异常

这个类演示了如何调用 BankAccount 的方法,并处理可能发生的自定义异常。

// Main.java
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000.0);
        // --- 场景1:正常取款 ---
        System.out.println("--- 场景1:正常取款 ---");
        try {
            account.withdraw(500.0);
        } catch (InsufficientBalanceException e) {
            // 因为余额是充足的,所以这段代码通常不会执行
            System.err.println("捕获到异常: " + e.getMessage());
        }
        System.out.println("操作后的余额: " + account.getBalance());
        System.out.println("----------------------------------\n");
        // --- 场景2:余额不足,触发自定义异常 ---
        System.out.println("--- 场景2:余额不足 ---");
        try {
            // 尝试取出比余额更多的钱
            account.withdraw(800.0);
        } catch (InsufficientBalanceException e) {
            // 捕获我们自定义的异常
            System.err.println("错误!捕获到自定义异常: " + e.getMessage());
            // 在这里可以执行恢复逻辑,比如通知用户、记录日志等
        } catch (IllegalArgumentException e) {
            // 捕获非法参数异常
            System.err.println("参数错误: " + e.getMessage());
        } finally {
            // finally 块中的代码无论是否发生异常都会执行
            System.out.println("场景2处理完毕,当前余额: " + account.getBalance());
        }
        System.out.println("----------------------------------\n");
        // --- 场景3:传入非法参数 ---
        System.out.println("--- 场景3:传入非法参数 ---");
        try {
            account.withdraw(-100.0);
        } catch (InsufficientBalanceException e) {
            System.err.println("捕获到余额不足异常: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            // 捕获非法参数异常
            System.err.println("错误!捕获到参数异常: " + e.getMessage());
        } finally {
            System.out.println("场景3处理完毕,当前余额: " + account.getBalance());
        }
    }
}

运行结果

--- 场景1:正常取款 ---
取款成功!当前余额: 500.0
操作后的余额: 500.0
----------------------------------
--- 场景2:余额不足 ---
错误!捕获到自定义异常: 取款失败:账户余额不足,当前余额: 500.0, 尝试取款: 800.0
场景2处理完毕,当前余额: 500.0
----------------------------------
--- 场景3:传入非法参数 ---
错误!捕获到参数异常: 取款金额必须大于0
场景3处理完毕,当前余额: 500.0

最佳实践

  1. 保持异常的粒度:为每种特定的错误情况创建一个独特的异常类。InvalidEmailExceptionInvalidPasswordExceptionInvalidUserInputException 更好。
  2. 提供有意义的错误信息:在构造异常时,传入清晰的描述性字符串,方便日志记录和问题排查。
  3. 优先使用非受检异常:对于大多数应用层代码,继承 RuntimeException 可以避免过多的 try-catchthrows 声明,使代码更简洁,只有当错误是可恢复的并且调用方必须处理时,才使用受检异常。
  4. 使用异常链:当一个异常是由另一个异常引起时(在解析数据库数据时发生 SQLException),使用 cause 参数将原始异常传递给新异常,有助于追踪问题的根本原因。
  5. 不要用异常来控制流程:异常机制是为处理错误而设计的,而不是用于常规的程序流程控制(如循环),用异常来处理 if-else 能搞定的逻辑会严重影响性能。
分享:
扫描分享到社交APP
上一篇
下一篇