Error 和 Exception 的核心区别(Java 实战中最常被问到、最容易混淆的概念之一)
一句话总结(面试/写代码时最简洁的回答):
- Error:严重系统错误,通常意味着 JVM 层面出了大问题,程序基本无药可救,不建议捕获。
- Exception:程序可以预期、可以处理的异常情况,业务逻辑或输入问题,应该捕获并处理。
1. 继承结构对比(最直观的图)
Throwable(所有可抛出的根)
├── Error
│ ├── VirtualMachineError
│ │ ├── OutOfMemoryError
│ │ ├── StackOverflowError
│ │ └── InternalError
│ ├── LinkageError
│ │ ├── NoClassDefFoundError
│ │ ├── ClassNotFoundError(极少见)
│ │ └── ...
│ └── AWTError / ThreadDeath / ...
└── Exception
├── RuntimeException(非检查异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ ├── ClassCastException
│ ├── ArithmeticException
│ ├── ConcurrentModificationException
│ └── ...
└── 其他 checked Exception(检查异常)
├── IOException
│ ├── FileNotFoundException
│ ├── SocketException
│ └── ...
├── SQLException
├── ParseException
├── InterruptedException
└── ...
2. 核心区别对比表(背下来就能应对 90% 的相关问题)
| 维度 | Error | Exception | RuntimeException(Exception 的子类) |
|---|---|---|---|
| 严重程度 | 极其严重,通常不可恢复 | 可预期、可恢复 | 可预期,但属于编程错误 |
| 是否必须处理 | 不建议捕获 | checked → 必须声明/捕获 unchecked → 可选 | 不强制处理(unchecked) |
| 典型场景 | JVM 内存耗尽、栈溢出、类加载失败 | 文件不存在、网络断开、数据库连接失败 | 空指针、下标越界、类型转换错误 |
| 出现时机 | 系统/虚拟机级别 | 应用程序/业务逻辑级别 | 程序员编码 Bug 级别 |
| 是否应该修复代码 | 基本不需要改代码,调 JVM 参数或硬件 | 通常要改代码或加防护 | 必须改代码(Bug) |
| 常见处理方式 | 记录日志 → 报警 → 重启节点 | try-catch / throws 处理或转换业务异常 | 基本不 catch,放着让上层或全局异常处理器处理 |
| 是否继承自 Throwable | 是 | 是 | 是 |
| 是否继承自 Exception | 否 | 是 | 是 |
3. 实战中最常见的错误用法(你很可能正在犯)
// 错误写法1:捕获 Error(几乎永远不要这么写)
try {
// 一些代码
} catch (Error e) { // ← 危险!
// 你以为你能处理 OutOfMemoryError?
// 大部分情况下捕获了也无能为力,还可能掩盖真正的问题
}
// 错误写法2:把所有异常都当成 RuntimeException 处理
try {
// ...
} catch (RuntimeException e) {
// 你把 IOException、SQLException 也漏掉了!
}
// 更危险写法(很多老项目还能看到)
try {
// ...
} catch (Exception e) { // 捕获了所有 Exception,包括 checked
// 直接吞掉或只打日志,什么都不做
log.error("出错了", e);
}
4. 推荐的实战处理策略(2025-2026 年主流写法)
// 1. 正常业务异常(checked) → 显式处理或转换
public User getUser(Long id) throws UserNotFoundException {
User user = userRepo.findById(id);
if (user == null) {
throw new UserNotFoundException(id); // 自定义业务异常
}
return user;
}
// 2. 编程错误(NPE、越界等) → 基本不 catch,放出去
public void process(int index) {
list.get(index); // 如果 index 非法 → 抛出 IndexOutOfBoundsException
// 上层统一处理或让程序 crash(开发/测试阶段)
}
// 3. 全局统一异常处理(Spring Boot 推荐)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<?> handleNPE(NullPointerException ex) {
// 返回 500 + 内部错误提示
return ResponseEntity
.internalServerError()
.body("系统繁忙,请稍后重试");
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<?> handleBusiness(BusinessException ex) {
// 返回 400 + 业务提示
return ResponseEntity.badRequest().body(ex.getMessage());
}
// 兜底
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAll(Exception ex) {
log.error("未预期异常", ex);
return ResponseEntity.internalServerError().body("服务器异常");
}
}
5. 快速记忆口诀
- Error → “JVM 炸了,程序员无能为力”
- checked Exception → “你知道会发生,必须提前说(throws)或处理(try)”
- RuntimeException → “程序员的锅,应该提前防(校验参数、判空等)”
一句话总结面试最稳的回答:
Error 表示严重的系统错误,程序员通常无法恢复,不建议捕获;
Exception 表示程序可以处理的异常情况,分为 checked(必须处理)和 unchecked(RuntimeException,通常是编程错误)。
你项目里有没有自定义过业务异常体系?
是全部继承 RuntimeException,还是有一部分 checked Exception?
或者你们是怎么处理全局异常的?可以聊聊你们的实际做法~