Java 中异常分哪两类,有什么区别?
Java 中异常分哪两类,有什么区别?
zhangchangsheng面试考察点
- 体系理解:面试官不仅仅是想知道 “受检异常” 和 “非受检异常” 这两个名词,更是想考察你是否理解 Java 异常体系的继承结构(
Throwable→Error/Exception)。 - 编译机制:受检异常的 “受检” 是什么意思?这涉及 Java 编译器如何强制处理异常。
- 实践能力:实际开发中如何选择异常类型?何时用受检异常、何时用非受检异常?
核心答案
Java 异常分为受检异常和非受检异常两大类。受检异常必须显式处理(try-catch 或 throws),否则编译不通过;非受检异常可以不处理,由 JVM 在运行时抛出。
| 对比维度 | 受检异常 | 非受检异常 |
|---|---|---|
| 父类 | Exception(除 RuntimeException) | Error、RuntimeException |
| 编译检查 | ✅ 必须处理,否则编译失败 | ❌ 不强制处理 |
| 处理方式 | try-catch 或 throws | 可选处理 |
| 代表场景 | IO 错误、数据库异常 | 空指针、数组越界、内存溢出 |
| 设计理念 | 可恢复的异常 | 不可恢复或程序 bug |
一句话总结:受检异常是 “编译器逼你处理”,非受检异常是 “运行时随缘处理”。
深度解析
一、Java 异常体系结构
flowchart TB
OBJ["Object"] --> THR["Throwable(所有异常的祖先)"]
THR --> ERR["Error(非受检异常,不需要显式处理)"]
THR --> EX["Exception"]
ERR --> VME["VirtualMachineError"]
ERR --> OOM["OutOfMemoryError"]
ERR --> SOE["StackOverflowError"]
EX --> IOE["IOException(受检异常,必须显式处理)"]
EX --> SQE["SQLException(受检异常)"]
EX --> RTE["RuntimeException(非受检异常,不需要显式处理)"]
RTE --> NPE["NullPointerException"]
RTE --> AIOOBE["ArrayIndexOutOfBoundsException"]
RTE --> CCE["ClassCastException"]
上图展示了 Java 异常体系的完整继承结构。关键要点:
Throwable:所有异常的父类,只有它的子类才能被throw或catchError:JVM 级别的严重错误,程序无法恢复(如OutOfMemoryError、StackOverflowError)Exception:程序可以处理的异常- 受检异常:
IOException、SQLException等,编译器强制处理 - 非受检异常:
RuntimeException及其子类,编译器不强制处理
- 受检异常:
二、受检异常 vs 非受检异常
import java.io.*; |
三、常见异常类型
| 类型 | 异常类 | 触发场景 |
|---|---|---|
| Error(非受检) | OutOfMemoryError | 内存不足 |
| StackOverflowError | 栈溢出(无限递归) | |
| NoClassDefFoundError | 类找不到 | |
| RuntimeException(非受检) | NullPointerException | 访问 null 对象 |
| ArrayIndexOutOfBoundsException | 数组越界 | |
| ClassCastException | 类型转换失败 | |
| ArithmeticException | 算术异常(如除零) | |
| IllegalArgumentException | 非法参数 | |
| 受检异常 | IOException | IO 操作失败 |
| SQLException | 数据库操作失败 | |
| ClassNotFoundException | 类加载失败 | |
| InterruptedException | 线程中断 |
四、如何选择异常类型?
flowchart TB
A{"发生异常时程序能否恢复?"} -->|"不能"| B["Error 或非受检异常"]
A -->|"能恢复"| C{"调用者能否合理处理这个异常?"}
C -->|"不能"| D["非受检异常"]
C -->|"能"| E["受检异常"]
EX["示例:文件不存在 → 调用者可以提示用户重新输入 → 受检异常<br/>空指针异常 → 程序 bug,调用者无法处理 → 非受检异常<br/>内存溢出 → 程序无法恢复 → Error"]
上图展示了选择异常类型的决策流程。关键原则:
- 受检异常:调用者可以合理恢复的情况(如文件不存在、网络超时)
- 非受检异常:程序 bug 或调用者无法处理的情况(如空指针、数组越界)
- Error:JVM 级别的严重问题,程序不应尝试捕获
五、最佳实践
// ✅ 好的做法:自定义业务异常(继承 RuntimeException) |
面试高频追问
throw和throws有什么区别?关键字 位置 作用 throw 方法体内 抛出一个异常对象 throws 方法签名上 声明方法可能抛出的异常类型 final、finally、finalize有什么区别?final:修饰符,类不能继承、方法不能重写、变量不能修改finally:try-catch的最后一块,无论是否异常都会执行finalize:Object的方法,GC 回收对象前调用(已废弃)
为什么要有受检异常?有人说它是 Java 的设计失误,你怎么看? 争议点:
- 支持者:强制处理异常,提高代码健壮性
- 反对者:导致代码冗余,异常传播污染方法签名,实际中常被滥用
- 趋势:现代框架(Spring)倾向于使用非受检异常
常见面试变体
- 变体一:”
Error和Exception有什么区别?” - 变体二:”
RuntimeException和普通Exception有什么区别?” - 变体三:”如何设计一个自定义异常类?”
记忆口诀
受检异常编译查,必须 try-catch 或 throws; 非受检异常运行时,不强制处理随缘抓; Error 严重难恢复,Exception 可处理。
总结
Java 异常分为受检异常(必须显式处理)和非受检异常(不强制处理)。受检异常适合可恢复的场景,非受检异常适合程序 bug 或无法恢复的场景。实际开发中,推荐使用非受检异常(继承 RuntimeException)设计业务异常,避免受检异常污染方法签名。
声明:本文转载自小哈的博客 《Java 中异常分哪两类,有什么区别?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
