1. 概述

本文将详细介绍 如何在 Java 中创建自定义异常

我们会演示如何定义和使用用户自定义的异常,涵盖 受检异常(checked)非受检异常(unchecked) 两种场景。对于有经验的开发者来说,这是提升代码可读性和维护性的常用手段,尤其在复杂业务系统中,踩坑多了自然会意识到统一异常处理的重要性。

2. 为什么需要自定义异常

Java 内置的异常体系已经覆盖了大多数通用场景,比如 IOExceptionNullPointerException 等。但实际开发中,仅靠这些“通用款”远远不够,尤其是在业务逻辑复杂的系统中。

引入自定义异常的主要原因包括:

  • 业务逻辑异常:某些错误是业务特有的,比如“用户余额不足”、“订单状态不允许退款”。用 IllegalArgumentException 虽然也能凑合,但语义模糊,不利于排查和日志分析。
  • 精细化异常处理:你想对某一类底层异常做特殊处理,但又不想影响其他情况。例如,同样是 FileNotFoundException,可能是文件名非法导致的,也可能是路径权限问题。通过自定义异常可以区分对待。

Java 异常分为两类:

  • 受检异常(checked exception):必须显式捕获或声明抛出,比如 IOException
  • 非受检异常(unchecked exception):继承自 RuntimeException,无需强制处理

接下来我们分别实现这两种自定义异常。

3. 自定义受检异常

受检异常的最大特点是:编译器会强制你处理它。如果你不 try-catchthrows,代码根本编不过。

我们来看一个读取文件首行的常见场景:

try (Scanner file = new Scanner(new File(fileName))) {
    if (file.hasNextLine()) return file.nextLine();
} catch(FileNotFoundException e) {
    // Logging, etc 
}

这段代码虽然能运行,但有个问题:当抛出 FileNotFoundException 时,你无法判断到底是“文件不存在”还是“文件名格式错误”。这对调用方来说是个黑盒 ❌。

这时候就需要一个更语义化的异常来说明问题 —— 比如 IncorrectFileNameException

如何定义?

✅ 只需继承 Exception 类即可:

public class IncorrectFileNameException extends Exception { 
    public IncorrectFileNameException(String errorMessage) {
        super(errorMessage);
    }
}

⚠️ 注意:必须提供一个接收 String 参数的构造函数,并传递给父类。否则你在抛出时没法带上具体的错误信息。

如何使用?

改造原来的逻辑,在发现文件名不合法时抛出自定义异常:

try (Scanner file = new Scanner(new File(fileName))) {
    if (file.hasNextLine())
        return file.nextLine();
} catch (FileNotFoundException e) {
    if (!isCorrectFileName(fileName)) {
        throw new IncorrectFileNameException("Incorrect filename : " + fileName );
    }
    // 其他处理...
}

看起来不错,但这里有个 经典踩坑点:我们把原始异常(FileNotFoundException)给丢了 ❌!

这意味着上层调用者看不到完整的调用栈,日志里也查不到根因,调试起来非常痛苦。

正确姿势:保留根因

✅ 解决方法:在自定义异常中增加 Throwable 构造参数,把原始异常链起来:

public class IncorrectFileNameException extends Exception { 
    
    public IncorrectFileNameException(String errorMessage) {
        super(errorMessage);
    }

    public IncorrectFileNameException(String errorMessage, Throwable err) {
        super(errorMessage, err);
    }
}

然后在 catch 块中传递原始异常:

try (Scanner file = new Scanner(new File(fileName))) {
    if (file.hasNextLine()) {
        return file.nextLine();
    }
} catch (FileNotFoundException err) {
    if (!isCorrectFileName(fileName)) {
        throw new IncorrectFileNameException(
          "Incorrect filename : " + fileName, err);
    }
    // ...
}

这样,异常堆栈中就能看到完整的链路:
IncorrectFileNameException ← caused by ← FileNotFoundException

这才是生产环境该有的样子 ✅。

4. 自定义非受检异常

非受检异常(即运行时异常)不需要强制捕获,适合用于“程序逻辑错误”或“不可恢复”的场景。

比如我们想检查文件名是否包含扩展名(.txt, .csv 等),这个判断只能在运行时完成,且一旦出错通常意味着调用方传参有问题。

定义方式

✅ 继承 RuntimeException 即可:

public class IncorrectFileExtensionException 
  extends RuntimeException {
    
    public IncorrectFileExtensionException(String errorMessage) {
        super(errorMessage);
    }

    public IncorrectFileExtensionException(String errorMessage, Throwable err) {
        super(errorMessage, err);
    }
}

使用示例

假设我们原来的逻辑中已经抛出了 IllegalArgumentException,现在我们可以进一步包装成更具体的异常:

try (Scanner file = new Scanner(new File(fileName))) {
    if (file.hasNextLine()) {
        return file.nextLine();
    } else {
        throw new IllegalArgumentException("Non readable file");
    }
} catch (FileNotFoundException err) {
    if (!isCorrectFileName(fileName)) {
        throw new IncorrectFileNameException(
          "Incorrect filename : " + fileName, err);
    }
} catch(IllegalArgumentException err) {
    if(!containsExtension(fileName)) {
        throw new IncorrectFileExtensionException(
          "Filename does not contain extension : " + fileName, err);
    }
}

这样做的好处是:

  • 上层可以精准捕获 IncorrectFileExtensionException 做特殊处理
  • 日志更清晰,定位问题更快
  • API 使用者更容易理解错误含义

5. 总结

自定义异常不是炫技,而是为了:

  • ✅ 提升代码可读性与可维护性
  • ✅ 实现业务语义的精准表达
  • ✅ 支持精细化的异常处理策略
  • ✅ 保留完整的异常链,便于排查问题

关键要点回顾:

类型 继承类 是否强制处理 适用场景
受检异常 Exception ✅ 是 业务可恢复场景,如文件名错误
非受检异常 RuntimeException ❌ 否 程序逻辑错误,如参数格式不合法

📌 最佳实践建议:

  • ✅ 一定要提供 String messageThrowable cause 两个构造函数
  • ✅ 异常类命名要清晰,推荐以 Exception 结尾,如 OrderNotFoundException
  • ✅ 不要滥用自定义异常,避免“异常爆炸”
  • ✅ 结合日志框架(如 SLF4J)记录完整上下文

文中示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-exceptions


原始标题:Create a Custom Exception in Java