Skip to content

异常处理

异常体系架构

                        Throwable

            ┌───────────────┴───────────────┐
            │                               │
          Error                         Exception
            │                               │
    ┌───────┴───────┐            ┌──────────┴──────────┐
    │               │            │                     │
 StackOverflow   OutOfMemory   RuntimeException    IOException
    Error         Error        (Unchecked)        (Checked)

                    ┌─────────┼─────────┬─────────┐
                    │         │         │         │
              NullPointer  ClassCast  IndexOut  IllegalArgument
                Exception  Exception  Bounds    Exception

Error 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 块总是执行");
        }
    }
}

💡 面试点

  1. 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");  // 不会执行
}
  1. 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 的区别

区别throwsthrow
位置方法声明处方法体内
类型声明异常类型抛出具体异常对象
数量可以声明多个一次抛出一个

自定义异常

自定义异常类

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⭐⭐⭐⭐⭐⭐⭐⭐⭐
自定义异常⭐⭐⭐⭐⭐⭐⭐⭐
异常链⭐⭐⭐⭐⭐⭐⭐⭐

⚠️ 易错点提醒

  1. finally 块中不要抛出异常
  2. catch 子类异常要放在父类前面
  3. finally 总是会执行,除了 System.exit()
  4. 抛出异常比捕获异常更好(fail fast)
  5. 不要用异常做流程控制