Appearance
异常处理
异常体系架构
Throwable
│
┌───────────────┴───────────────┐
│ │
Error Exception
│ │
┌───────┴───────┐ ┌──────────┴──────────┐
│ │ │ │
StackOverflow OutOfMemory RuntimeException IOException
Error Error (Unchecked) (Checked)
│
┌─────────┼─────────┬─────────┐
│ │ │ │
NullPointer ClassCast IndexOut IllegalArgument
Exception Exception Bounds ExceptionError vs Exception
| 类型 | 说明 | 处理方式 |
|---|---|---|
| Error | 系统级错误,程序无法处理 | 不捕获,让程序崩溃 |
| Exception | 程序可处理的异常 | 应该捕获并处理 |
常见的 Error:
StackOverflowError- 栈溢出(递归没退出)OutOfMemoryError- 内存溢出NoClassDefFoundError- 类找不到
常见的 Exception:
| 类型 | 示例 | 说明 |
|---|---|---|
| Checked (受检异常) | IOException, SQLException | 编译器强制要求处理 |
| Unchecked (非受检异常) | NullPointerException, IndexOutOfBoundsException | 运行时异常,可选处理 |
异常处理语法
try-catch-finally
java
public class ExceptionDemo {
public void demo() {
try {
// 可能抛出异常的代码
int result = 10 / 0;
System.out.println("不会执行到这里");
} catch (ArithmeticException e) {
// 处理算术异常
System.out.println("捕获到除零异常:" + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
// 处理其他异常(父类 catch 要放在后面)
System.out.println("捕获到其他异常");
} finally {
// 无论是否异常都会执行
System.out.println("finally 块总是执行");
}
}
}💡 面试点:
- finally 一定会执行吗?
java
try {
System.out.println("try");
return;
} finally {
System.out.println("finally"); // 仍然会执行
}
// 输出:try, finally
// 但有一种情况 finally 不会执行:
try {
System.exit(0); // JVM 退出
} finally {
System.out.println("finally"); // 不会执行
}- catch 的顺序很重要!
java
try {
// 可能抛出 NullPointerException 或 ArithmeticException
} catch (Exception e) { // 父类放前面
// 永远不会执行下面的 catch
} catch (ArithmeticException e) { // 编译错误!
}try-with-resources(Java 7+)
传统写法:
java
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}新写法(推荐):
java
// 自动关闭资源,实现 AutoCloseable 接口的资源会自动调用 close()
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// 无需 finally,reader.close() 自动调用💡 面试点:try-with-resources 有什么优势?
java
// 可以同时管理多个资源
try (
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
) {
while (rs.next()) {
// 处理结果
}
} catch (SQLException e) {
e.printStackTrace();
}
// 三个资源都会自动关闭,无需手动 finally抛出异常
throws vs throw
java
public class ThrowDemo {
// throws - 声明方法可能抛出的异常(告诉调用者)
public void readFile() throws IOException {
FileReader reader = new FileReader("file.txt");
// 可能抛出 IOException
reader.read();
reader.close();
}
// throw - 手动抛出异常
public void validate(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法:" + age);
}
System.out.println("年龄合法");
}
// 抛出受检异常
public void method() throws MyCheckedException {
throw new MyCheckedException("自定义受检异常");
}
// 抛出运行时异常
public void method2() {
throw new MyRuntimeException("自定义运行时异常");
}
}throws 和 throw 的区别:
| 区别 | throws | throw |
|---|---|---|
| 位置 | 方法声明处 | 方法体内 |
| 类型 | 声明异常类型 | 抛出具体异常对象 |
| 数量 | 可以声明多个 | 一次抛出一个 |
自定义异常
自定义异常类
java
// 运行时异常(推荐)
public class BusinessException extends RuntimeException {
private int code;
private String detail;
public BusinessException(String message) {
super(message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public int getCode() {
return code;
}
}
// 受检异常
public class ValidationException extends Exception {
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
}自定义异常使用示例
java
public class UserService {
public void register(String username, String password) {
// 校验用户名
if (username == null || username.trim().isEmpty()) {
throw new BusinessException(1001, "用户名不能为空");
}
if (username.length() < 3 || username.length() > 20) {
throw new BusinessException(1002, "用户名长度必须在3-20之间");
}
// 校验密码
if (password == null || password.length() < 6) {
throw new BusinessException(1003, "密码长度不能少于6位");
}
// 业务逻辑...
}
}
// 调用方处理
public class UserController {
public void handleRegister(HttpRequest request) {
try {
String username = request.getParam("username");
String password = request.getParam("password");
userService.register(username, password);
} catch (BusinessException e) {
System.out.println("业务异常:" + e.getMessage());
System.out.println("错误码:" + e.getCode());
}
}
}常见异常详解
NullPointerException(空指针异常)
💡 面试高频:最常见的异常,如何避免?
java
// ❌ 常见错误
String str = null;
System.out.println(str.length()); // NPE
// ✅ 防御式检查
if (str != null) {
System.out.println(str.length());
}
// ✅ 使用 Optional (Java 8+)
Optional<String> opt = Optional.ofNullable(str);
opt.ifPresent(s -> System.out.println(s.length()));
// ✅ 使用 Objects.requireNonNull
Objects.requireNonNull(str, "str 不能为 null");ArrayIndexOutOfBoundsException
java
int[] arr = {1, 2, 3};
// ❌ 错误
System.out.println(arr[3]); // 数组越界
// ✅ 防御式检查
if (index >= 0 && index < arr.length) {
System.out.println(arr[index]);
}ClassCastException
java
Object obj = "Hello";
// ❌ 错误
Integer num = (Integer) obj; // ClassCastException
// ✅ 用 instanceof 检查
if (obj instanceof Integer) {
Integer num = (Integer) obj;
}
// ✅ Java 16+ Pattern Matching
if (obj instanceof Integer num) {
System.out.println(num);
}NumberFormatException
java
String str = "123a";
// ❌ 错误
int num = Integer.parseInt(str); // NumberFormatException
// ✅ 提前校验
try {
int num = Integer.parseInt(str);
} catch (NumberFormatException e) {
System.out.println("不是有效的数字");
}
// ✅ 使用正则表达式校验
if (str.matches("\\d+")) {
int num = Integer.parseInt(str);
}异常链与异常转发
异常链(Exception Chaining)
java
public class ExceptionChain {
public void methodA() {
try {
methodB();
} catch (Exception e) {
// 保留原始异常信息
throw new BusinessException("业务处理失败", e);
}
}
public void methodB() {
try {
// 可能抛出 SQLException
} catch (SQLException e) {
// 包装成新的异常
throw new BusinessException("数据库操作失败", e);
}
}
}
// 获取原始异常
try {
methodA();
} catch (BusinessException e) {
System.out.println("业务异常:" + e.getMessage());
System.out.println("原始异常:" + e.getCause().getMessage());
}异常转发示例
java
public class ExceptionWrapper {
// Servlet 异常处理典型模式
public void handleRequest(HttpRequest request) {
try {
validate(request);
process(request);
save(request);
} catch (ValidationException e) {
// 校验异常 -> 400 Bad Request
throw new HttpException(400, "参数校验失败:" + e.getMessage());
} catch (BusinessException e) {
// 业务异常 -> 500 Internal Server Error
throw new HttpException(500, "业务处理失败:" + e.getMessage());
} catch (Exception e) {
// 未知异常 -> 500
throw new HttpException(500, "系统异常", e);
}
}
}异常处理最佳实践
┌─────────────────────────────────────────────────────────────────┐
│ 异常处理十大原则 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 不要捕获 Throwable - 除非特殊原因 │
│ 2. 早抛出,晚捕获 - fail fast │
│ 3. 不要吞掉异常 - 至少要记录日志 │
│ 4. 指定具体异常类型 - 不要捕获所有 Exception │
│ 5. 不要在 finally 中抛出异常 - 可能覆盖原始异常 │
│ 6. 优先使用 try-with-resources - 自动关闭资源 │
│ 7. 异常信息要清晰 - 便于排查问题 │
│ 8. 不要用异常做流程控制 - 性能开销大 │
│ 9. 自定义异常要有错误码 - 便于系统对接 │
│ 10. 异常要文档化 - 用 @throws 声明 │
└─────────────────────────────────────────────────────────────────┘反面示例 vs 正确示例
java
// ❌ 反面示例
try {
doSomething();
} catch (Exception e) {
// 吞掉异常,什么都不做
}
// ✅ 正确示例
try {
doSomething();
} catch (SpecificException e) {
logger.error("操作失败", e);
throw new BusinessException("操作失败", e);
}
// ❌ 反面示例
catch (Throwable t) {
throw t; // 不要捕获 Throwable
}
// ✅ 正确示例
catch (Exception e) {
// 处理异常
}总结
异常处理核心知识点:
| 知识点 | 面试频率 | 实战重要性 |
|---|---|---|
| Error vs Exception | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Checked vs Unchecked | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| try-catch-finally | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| try-with-resources | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| throws vs throw | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 自定义异常 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 异常链 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
⚠️ 易错点提醒:
- finally 块中不要抛出异常
- catch 子类异常要放在父类前面
- finally 总是会执行,除了
System.exit() - 抛出异常比捕获异常更好(fail fast)
- 不要用异常做流程控制