面试必备:Java 异常体系全解析
异常体系全景图
Throwable (可抛出的)
│
├── Error (错误) ← 程序无法处理,编译器不检查
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ ├── NoClassDefFoundError
│ └── ...
│
└── Exception (异常) ← 可以处理
│
├── RuntimeException (运行时异常/Unchecked)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ ├── ClassCastException
│ ├── ArithmeticException
│ └── ...
│
└── 其他异常 (Checked) ← 编译器强制处理
├── IOException
├── SQLException
├── FileNotFoundException
├── ClassNotFoundException
└── ...
Error vs Exception
Error(错误)
程序无法处理的严重问题,不需要捕获,也不应该捕获。
// 常见的 Error
OutOfMemoryError // 内存溢出
StackOverflowError // 栈溢出
NoClassDefFoundError // 类找不到
VirtualMachineError // JVM 虚拟机错误
典型场景:
// 递归没有终止条件,会 StackOverflowError
public void recursion() {
recursion(); // 无限递归
}
Exception(异常)
程序可以处理的问题,需要捕获或声明抛出。
Checked vs Unchecked
这是面试的高频考点!
Checked Exception(受检异常)
编译器强制要求处理的异常。
// 必须 try-catch 或 throws
try {
FileReader reader = new FileReader("file.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Unchecked Exception(运行时异常)
编译器不强制处理,属于程序逻辑错误。
// 不需要 try-catch,编译器不会报错
String s = null;
s.length(); // NullPointerException,但编译器不管
对比
| 特性 | Checked | Unchecked |
|---|---|---|
| 编译器检查 | 是 | 否 |
| 必须处理 | 是 | 否 |
| 常见例子 | IOException, SQLException | NullPointerException |
| 性质 | 外部因素,环境可能出错 | 程序 bug,逻辑错误 |
面试回答模板
"Checked Exception 是编译器强制处理的异常,表示外部因素导致的错误,比如 IO 操作、数据库访问。Unchecked Exception(运行时异常)是程序逻辑错误,比如空指针、数组越界,编译器不强制处理。"
异常处理语法
1. try-catch-finally
try {
// 可能抛出异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 捕获特定异常
System.out.println("除数不能为0");
} catch (Exception e) {
// 捕获其他异常(大的放后面)
e.printStackTrace();
} finally {
// 无论是否异常都执行
System.out.println("清理资源");
}
2. try-with-resources(Java 7+)
自动关闭资源,不用写 finally:
// 传统写法
try {
FileReader reader = new FileReader("file.txt");
// ... 读取文件
} finally {
reader.close(); // 容易忘记或出错
}
// try-with-resources(推荐)
try (FileReader reader = new FileReader("file.txt")) {
// ... 读取文件
} // 自动关闭,不用写 finally
3. throws vs throw
// throws:声明方法可能抛出的异常(方法签名的一部分)
public void readFile() throws IOException {
// 方法内部不处理,让调用者处理
FileReader reader = new FileReader("file.txt");
}
// throw:手动抛出异常
public void validate(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
}
异常链
保留原始异常信息:
try {
// 业务逻辑
doSomething();
} catch (IOException e) {
// 包装成新的业务异常,保留原异常
throw new BusinessException("处理失败", e);
}
常见面试问题
Q1:finally 和 return 的执行顺序?
public int test() {
try {
return 1;
} finally {
// return 会把返回值暂存,执行完 finally 再返回
// 这里的修改不会影响返回值
return 2;
}
}
// 返回值:2
Q2:finally 一定会执行吗?
几乎一定,但有例外:
// 情况1:System.exit()
try {
System.exit(0);
} finally {
// 不会执行
}
// 情况2:线程被杀死
// 如果当前线程被 kill,finally 不会执行
Q3:为什么 finally 中不建议 return?
public int bad() {
try {
return 1;
} finally {
return 2; // 掩盖了 try 中的异常!
}
}
Q4:OOM 和 StackOverflowError 是 Error 还是 Exception?
两者都是 Error,不是 Exception。
OutOfMemoryError extends ErrorStackOverflowError extends Error
最佳实践
1. 不要捕获 Throwable
// ❌ 不好
catch (Throwable t) {
// 会捕获 Error,不应该捕获
}
// ✓ 推荐:只捕获能处理的异常
catch (IOException e) {
// 处理 IO 错误
}
2. 不要吞掉异常
// ❌ 不好
catch (Exception e) {
// 什么都不做,异常消失了
}
// ✓ 推荐:至少打印日志
catch (Exception e) {
log.error("处理失败", e);
throw e; // 或者抛出
}
3. 异常要精确
// ❌ 不精确
catch (Exception e) { }
// ✓ 推荐:精确捕获
catch (FileNotFoundException e) { }
catch (IOException e) { }
4. 不要用异常做流程控制
// ❌ 不好:异常是用来处理意外情况的
try {
for (int i = 0; ; i++) {
arr[i] = i;
}
} catch (ArrayIndexOutOfBoundsException e) {
// 循环正常结束了
}
// ✓ 推荐:用正常判断
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
总结
异常体系:
├── Error(错误)→ 严重,不能处理
└── Exception(异常)→ 可处理
├── Checked(受检)→ 编译器强制处理
└── Unchecked(运行时)→ 逻辑错误
关键点:
- Checked vs Unchecked 的区别和使用场景
- try-catch-finally 的执行顺序
- throws vs throw 的区别
- 不要吞掉异常,不要用异常做流程控制