Java 异常体系

在Java中,异常体系以Throwable为根类,分为两大分支:Error(错误)Exception(异常)。它们的设计目的是区分“程序无法处理的严重问题”和“程序可以处理的意外情况”,具体分类及作用如下。

一、Throwable:所有异常/错误的根类

Throwable是Java异常体系的顶层父类,定义了所有错误和异常的通用属性(如错误信息、堆栈跟踪)和方法(如getMessage()printStackTrace())。它有两个直接子类:ErrorException

二、Error:程序无法处理的严重错误

Error表示JVM或系统级别的严重错误,通常是程序自身无法恢复的情况。这类错误发生时,JVM可能终止运行,开发者一般不需要(也无法)在代码中捕获或处理,而是需要从根源上解决(如优化代码、调整配置)。

常见Error类型及作用:

  1. OutOfMemoryError(内存溢出错误)
    当JVM无法分配足够内存时抛出(如创建大量对象未释放)。
    作用:提示内存资源耗尽,需通过调整JVM内存参数(如-Xmx)或优化对象生命周期解决。

  2. StackOverflowError(栈溢出错误)
    当方法调用栈深度超过JVM限制时抛出(如无限递归调用)。
    作用:提示程序存在递归逻辑错误,需修复递归终止条件。

  3. NoClassDefFoundError(类定义未找到错误)
    编译时存在某个类,但运行时无法找到其.class文件(如类路径配置错误、Jar包缺失)。
    作用:提示类加载失败,需检查类路径或依赖是否完整。

  4. VirtualMachineError(虚拟机错误)
    泛指JVM自身出现的错误(如OutOfMemoryErrorStackOverflowError都是其子类)。
    作用:标识JVM无法正常工作,需排查环境或资源问题。

三、Exception:程序可以处理的异常

Exception表示程序运行中出现的可预期、可处理的意外情况(如文件不存在、参数错误)。根据编译时是否强制处理,又分为两类:受检异常(Checked Exception)非受检异常(Unchecked Exception)

1. 受检异常(Checked Exception)

编译时必须显式处理(捕获try-catch或声明throws)的异常,通常与外部资源交互相关(如文件、网络、数据库),开发者必须考虑这些场景并编写处理逻辑。

常见受检异常及作用:

  • IOException(输入输出异常)
    处理文件、流操作时的错误(如FileNotFoundException文件未找到、EOFException文件提前结束)。
    作用:强制开发者处理外部存储的不可靠性(如文件可能被删除)。

  • SQLException(数据库访问异常)
    数据库操作时的错误(如连接失败、SQL语法错误、表不存在)。
    作用:强制处理数据库交互的不确定性(如网络波动导致连接中断)。

  • ClassNotFoundException(类未找到异常)
    动态加载类时(如Class.forName())无法找到指定类。
    作用:提示反射或动态加载时的类路径问题,需显式处理依赖缺失场景。

  • InterruptedException(中断异常)
    线程在休眠(sleep())或等待(wait())时被其他线程中断。
    作用:强制处理线程协作中的中断信号,保证多线程逻辑的正确性。

2. 非受检异常(Unchecked Exception)

编译时无需强制处理的异常(继承自RuntimeException),通常是编程逻辑错误导致的,开发者应通过规范代码避免(而非捕获处理)。

常见非受检异常及作用:

  • NullPointerException(空指针异常)
    调用null对象的方法或属性时抛出(如String s = null; s.length();)。
    作用:提示代码中存在未判空的风险,需在使用对象前检查非空。

  • IndexOutOfBoundsException(索引越界异常)
    访问数组、集合时索引超出范围(如List.get(10)但列表只有5个元素),其子类有ArrayIndexOutOfBoundsException(数组)、StringIndexOutOfBoundsException(字符串)。
    作用:提示索引操作逻辑错误,需检查索引范围。

  • IllegalArgumentException(非法参数异常)
    方法接收到不合法的参数时抛出(如传递负数给“年龄”参数)。
    作用:强制调用者传递符合要求的参数,通常由方法内部主动抛出(throw new IllegalArgumentException(...))。

  • ArithmeticException(算术异常)
    算术运算错误(如除以零10 / 0)。
    作用:提示数学运算逻辑错误,需避免无效运算。

  • ClassCastException(类型转换异常)
    强制类型转换失败时抛出(如Object obj = "hello"; Integer i = (Integer)obj;)。
    作用:提示类型转换逻辑错误,需通过instanceof检查类型兼容性。

四、自定义异常

除了Java内置异常,实际开发中常需定义业务相关的自定义异常,通常继承Exception(受检)或RuntimeException(非受检),用于区分业务逻辑错误与系统异常。

示例及作用:

// 自定义受检异常(业务异常:用户余额不足)
public class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

// 自定义非受检异常(业务异常:订单已关闭)
public class OrderClosedException extends RuntimeException {
    public OrderClosedException(String message) {
        super(message);
    }
}

作用:

  • 使异常类型与业务场景绑定(如“余额不足”“订单过期”),便于上层代码针对性处理(如返回用户友好提示)。
  • 区分“系统错误”(如IOException)和“业务错误”(如自定义异常),简化异常处理逻辑。

总结

Java异常体系的设计核心是分层处理不同级别的问题

  • Error:JVM级别的严重错误,无需处理,需解决根源问题;
  • 受检Exception:外部资源相关的可预期错误,强制处理,保证程序健壮性;
  • 非受检Exception:编程逻辑错误,需通过规范代码避免,而非过度捕获;
  • 自定义异常:适配业务场景,使错误处理更清晰。

合理使用异常体系,能有效提升代码的可靠性和可维护性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐