八、java异常类

0 阅读7分钟

Java 异常体系

(从体系结构、继承关系、底层原理、语法细节、企业坑、最佳实践、面试陷阱全覆盖)


异常核心认知

  1. 异常本质:程序运行时出现的非正常事件,会中断正常指令流。
  2. Java 异常是对象:所有异常都是 Throwable 子类的实例。
  3. 异常处理目的
    • 避免程序直接崩溃
    • 给出友好提示
    • 保证资源释放
    • 便于问题定位
  4. 异常处理不是用来控制业务逻辑的,这是企业大忌。

1. 完整异常体系结构(最顶层)

所有异常/错误都继承自 java.lang.Throwable

java.lang.Throwable
├── ① Error(错误:JVM 层面,系统级,无法处理)
└── ② Exception(异常:应用级,程序员可处理)
    ├── ②-1 RuntimeException(运行时异常 / 非受检异常)
    └── ②-2 受检异常(编译期异常,非 RuntimeException

2. 顶层父类:Throwable

只有 Throwable 及其子类才能被 throw、try-catch 处理

核心方法(必须掌握)

  • getMessage():获取异常简单描述信息
  • getLocalizedMessage():本地化信息(一般同 getMessage)
  • printStackTrace():打印异常堆栈(最常用)
  • printStackTrace(PrintStream s):输出到流
  • getStackTrace():获取堆栈跟踪数组(StackTraceElement[])
  • initCause(Throwable cause):设置异常根源
  • getCause():获取异常根本原因(底层异常)

3. Error(错误)

定义

  • JVM 内部错误、资源耗尽、系统崩溃
  • 应用程序无法捕获、无法恢复、不应该捕获
  • 不需要 try-catch,也不应该 throws

常见 Error(必须认识)

  1. VirtualMachineError
    • StackOverflowError:栈溢出(递归死循环、方法调用层级过深)
    • OutOfMemoryError:OOM 内存溢出(堆溢出、直接内存溢出)
  2. NoClassDefFoundError:类找不到(依赖缺失、打包错误)
  3. NoSuchMethodError:方法不存在(版本冲突)
  4. LinkageError:类加载/链接错误
  5. AssertionError:断言失败

企业原则

遇到 Error 直接让程序挂掉,不要捕获,捕获也修复不了


4. Exception(异常)

分为两大类:

  • RuntimeException:运行时异常(非受检)
  • 受检异常(Checked Exception):编译期强制处理

4.1 受检异常 Checked Exception

特点

  1. 编译期强制处理:要么 try-catch,要么 throws
  2. 不是代码逻辑 Bug,而是外部环境不可控问题
  3. 继承自 Exception,但不继承 RuntimeException

常见受检异常

  • IOException:IO 异常(文件、网络、流)
    • FileNotFoundException
    • EOFException
    • SocketException
  • SQLException:数据库异常
  • ParseException:日期/格式解析异常
  • ClassNotFoundException:类加载失败
  • InterruptedException:线程中断
  • IllegalAccessException

使用场景

底层框架、IO、数据库、网络等外部依赖操作


4.2 运行时异常 RuntimeException(非受检)

特点

  1. 编译期不检查,运行时才抛出
  2. 本质是代码逻辑 Bug
  3. 可处理可不处理,一般不捕获,由全局统一处理

最常见、企业高频出现的运行时异常

  1. NullPointerException:空指针异常(NPE)→ 出现率第一
  2. IndexOutOfBoundsException
    • ArrayIndexOutOfBoundsException 数组越界
    • StringIndexOutOfBoundsException 字符串越界
  3. ClassCastException:类型转换异常
  4. IllegalArgumentException:非法参数
  5. IllegalStateException:状态非法
  6. ArithmeticException:算术异常(除 0)
  7. UnsupportedOperationException:不支持的操作(如 Arrays.asList 后 add)
  8. ConcurrentModificationException:集合遍历中修改(fail-fast)
  9. NumberFormatException:数字格式转换失败
  10. UnsupportedClassVersionError:版本不兼容

5. 异常处理五大关键字详解

5.1 try

  • 包裹可能抛出异常的代码
  • 不能单独出现,必须配合 catch 或 finally
  • try 代码块出现异常后,异常行之后的代码不再执行
try {
    // 正常逻辑
    // 异常发生处 → 立即跳转到匹配的 catch
    // 后续代码不执行
}

5.2 catch

规则

  1. 可以有多个 catch
  2. 异常范围从小到大(子类在前,父类在后)
  3. 范围顺序错误会编译报错
try {
} catch (NullPointerException e) {
} catch (RuntimeException e) {
} catch (Exception e) {
}

多异常合并(JDK7+)

catch (IOException | SQLException e) {
    // 处理多个异常
    // 异常变量默认 final
}

5.3 finally

核心保证

无论是否异常、无论是否 return、无论是否 break,finally 一定执行

唯一不执行的情况

  • System.exit(0)(JVM 退出)
  • 线程被强制终止
  • JVM 崩溃

执行顺序(面试必考)

  1. try 代码执行
  2. 遇到 return → 先保存返回值
  3. 执行 finally
  4. 真正 return

5.4 throw

手动抛出一个异常对象

if (age < 0 || age > 150) {
    throw new IllegalArgumentException("年龄非法:" + age);
}

特点:

  • 一旦执行 throw,方法立即结束
  • 后面代码不执行

5.5 throws

方法声明异常,表示“我不处理,交给上层处理”

public void readFile() throws IOException, SQLException {
}

throw vs throws(面试必考)

  • throw:方法内,手动抛一个异常对象
  • throws:方法声明,声明多个异常类型
  • throw 执行后方法终止;throws 只是声明

6. 异常执行流程(底层机制)

  1. 发生异常 → JVM 创建对应异常对象
  2. JVM 从当前方法开始向上回溯调用栈
  3. 寻找匹配的 catch 块
  4. 找到 → 执行 catch
  5. 找不到 → 线程终止,打印堆栈
  6. 无论如何 finally 一定执行

7. finally 三大企业致命坑(90% 开发踩过)

坑 1:finally 中 return 会覆盖 try/catch 的返回值

public static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}
// 结果:2

企业严禁在 finally 中 return

坑 2:finally 抛出异常会“吃掉”原始异常

try {
    throw new RuntimeException("业务异常");
} finally {
    throw new RuntimeException("释放资源异常");
}
// 原始异常完全丢失,无法排查问题

坑 3:finally 中出现死循环/阻塞,永远不退出

try { return; }
finally {
    while (true) {}
}
// 方法永远不结束

8. try-with-resources(JDK7+ 企业标准)

作用

自动关闭实现 AutoCloseable 接口的资源(流、连接、锁)

语法

try (
    FileInputStream fis = new FileInputStream("a.txt");
    BufferedReader br = new BufferedReader(fis)
) {
    // 业务代码
} catch (IOException e) {
    e.printStackTrace();
}

底层原理

编译后自动生成:

  • try
  • catch
  • finally → 调用 close()

优势

  1. 代码极简
  2. 不会丢失异常(异常被 suppressed)
  3. 避免忘记关流导致文件句柄泄漏、连接泄漏
  4. 企业开发强制使用

获取被抑制的异常

Throwable[] suppressed = e.getSuppressed();

9. 异常链(异常包裹)

底层异常抛到上层时,保留原始异常信息,避免丢失根源

try {
    // ...
} catch (IOException e) {
    // 把 e 作为 cause 传入
    throw new BusinessException("文件读取失败", e);
}
  • e.getCause() 可获取底层异常
  • 日志打印会输出完整堆栈

企业规范:所有包装异常必须传入 cause


10. 自定义异常(企业开发必备)

10.1 自定义运行时异常(推荐)

public class BusinessException extends RuntimeException {

    private Integer code;

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

10.2 自定义受检异常

public class CheckedBusinessException extends Exception {
    // ... 同上
}

企业规范

  • 业务异常全部使用 RuntimeException
  • 方便全局捕获,不需要层层 throws
  • 配合 SpringBoot 全局异常处理返回统一 JSON

11. 企业异常处理规范(非常重要)

  1. 禁止捕获 Throwable/Error
  2. 禁止空 catch 块(至少打日志)
  3. 禁止用异常做 if/else 业务判断
  4. 禁止在循环中频繁 try-catch(性能极差)
  5. 受检异常尽量底层消化,不要抛到 Controller
  6. 异常信息要包含:参数、场景、原因,便于排查
  7. 禁止在 finally 中 return / throw
  8. 全局统一异常处理:@RestControllerAdvice + @ExceptionHandler
  9. 包装异常必须传入 cause,否则无法定位根因
  10. 不要重复捕获、重复包装异常(堆栈混乱)

12. 运行时异常 vs 受检异常 企业选择

现代企业(SpringBoot / 微服务)

全部使用运行时异常

原因:

  • 代码干净
  • 无层层 throws
  • 全局统一捕获
  • 微服务之间异常传递更友好

受检异常适用场景

  • JDK 底层 IO
  • 数据库驱动
  • 必须强制调用者处理的底层操作

13. 高频面试题(深度版)

  1. Throwable、Exception、Error 的区别?
  2. 受检异常和运行时异常区别?
  3. finally 一定执行吗?什么时候不执行?
  4. finally 在 return 前还是 return 后执行?
  5. throw 和 throws 区别?
  6. 异常链是什么?为什么要传 cause?
  7. try-with-resources 原理?
  8. 为什么不建议捕获 Exception/Throwable?
  9. 自定义异常继承 Exception 还是 RuntimeException?
  10. 出现 NPE 一般是什么原因?如何避免?

14. 一张图彻底记住异常体系

Throwable
   ├─ Error:JVM 错误,不可处理,StackOverflow / OOM
   └─ Exception
        ├─ 受检异常:编译必须处理 → IO/SQL
        └─ RuntimeException:代码Bug → NPE/越界/转换异常

处理方式:
   try-catch-finally
   try-with-resources(自动关流)
   throw 手动抛
   throws 方法声明
   自定义异常 + 全局捕获