Skip to content

异常处理

异常处理是 Java 程序中处理错误和异常情况的重要机制。

异常概述

异常层次结构

Throwable
├── Error(错误)
│   ├── VirtualMachineError
│   ├── OutOfMemoryError
│   └── StackOverflowError
└── Exception(异常)
    ├── RuntimeException(运行时异常,非检查型)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ArithmeticException
    │   ├── ClassCastException
    │   └── IllegalArgumentException
    └── 其他异常(检查型异常)
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── FileNotFoundException

异常分类

  • Error:JVM 无法解决的严重问题,如内存溢出
  • 检查型异常:编译时必须处理的异常
  • 非检查型异常:运行时异常,编译时不强制处理

异常处理

try-catch

java
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除零错误: " + e.getMessage());
}

// 多个 catch 块
try {
    int[] arr = new int[5];
    arr[10] = 50;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界");
} catch (NullPointerException e) {
    System.out.println("空指针");
} catch (Exception e) {
    System.out.println("其他异常");
}

// Java 7+ 多异常捕获
try {
    // 可能抛出多种异常的代码
} catch (IOException | SQLException e) {
    System.out.println("IO 或 SQL 异常: " + e.getMessage());
}

try-catch-finally

java
try {
    // 可能出错的代码
    FileReader file = new FileReader("test.txt");
} catch (FileNotFoundException e) {
    System.out.println("文件未找到");
} finally {
    // 无论是否异常都会执行
    System.out.println("清理资源");
}

// finally 常用于资源清理
FileReader reader = null;
try {
    reader = new FileReader("test.txt");
    // 读取操作
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try-with-resources

java
// Java 7+ 自动资源管理
try (FileReader reader = new FileReader("test.txt");
     BufferedReader br = new BufferedReader(reader)) {
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}
// 资源自动关闭,无需 finally

// 自定义资源类需要实现 AutoCloseable
class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("使用资源");
    }

    @Override
    public void close() {
        System.out.println("关闭资源");
    }
}

try (MyResource resource = new MyResource()) {
    resource.doSomething();
}

throw 关键字

java
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("年龄必须在 0-150 之间");
    }
    this.age = age;
}

// 抛出自定义异常
public void withdraw(double amount) throws InsufficientFundsException {
    if (amount > balance) {
        throw new InsufficientFundsException("余额不足");
    }
    balance -= amount;
}

throws 关键字

java
// 声明方法可能抛出的异常
public void readFile(String path) throws IOException, FileNotFoundException {
    FileReader reader = new FileReader(path);
    // ...
}

// 调用者必须处理或继续声明
public void processFile() {
    try {
        readFile("test.txt");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

自定义异常

创建自定义异常

java
// 自定义检查型异常
public class InsufficientFundsException extends Exception {
    private double balance;
    private double amount;

    public InsufficientFundsException(double balance, double amount) {
        super("余额不足: 当前余额 " + balance + ", 取款金额 " + amount);
        this.balance = balance;
        this.amount = amount;
    }

    public double getBalance() { return balance; }
    public double getAmount() { return amount; }
}

// 自定义运行时异常
public class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }

    public InvalidAgeException(int age) {
        super("无效年龄: " + age);
    }
}

使用自定义异常

java
public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于0");
        }
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        balance -= amount;
        System.out.println("取款成功,余额: " + balance);
    }

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException(age);
        }
        this.age = age;
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        try {
            account.withdraw(1500);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("当前余额: " + e.getBalance());
        }
    }
}

异常链

java
public class DataProcessor {
    public void process(String data) throws DataProcessingException {
        try {
            // 解析数据
            int value = Integer.parseInt(data);
        } catch (NumberFormatException e) {
            // 保留原始异常信息
            throw new DataProcessingException("数据解析失败", e);
        }
    }
}

public class DataProcessingException extends Exception {
    public DataProcessingException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 使用
try {
    processor.process("abc");
} catch (DataProcessingException e) {
    System.out.println(e.getMessage());
    System.out.println("原因: " + e.getCause());
}

异常最佳实践

1. 只捕获能处理的异常

java
// 不推荐:捕获所有异常
try {
    // 很多操作
} catch (Exception e) {
    e.printStackTrace();
}

// 推荐:只捕获特定异常
try {
    fileReader = new FileReader(path);
} catch (FileNotFoundException e) {
    System.out.println("文件不存在: " + path);
}

2. 不要忽略异常

java
// 不推荐
try {
    doSomething();
} catch (Exception e) {
    // 空的 catch 块
}

// 推荐:至少记录日志
try {
    doSomething();
} catch (Exception e) {
    logger.error("操作失败", e);
    // 或者重新抛出
    throw new RuntimeException("操作失败", e);
}

3. 使用具体的异常类型

java
// 不推荐
public void validate(String input) throws Exception {
    // ...
}

// 推荐
public void validate(String input) throws ValidationException {
    // ...
}

4. 提供有意义的异常信息

java
// 不推荐
throw new IllegalArgumentException("Invalid input");

// 推荐
throw new IllegalArgumentException("用户名不能为空");

5. 尽早失败

java
public void transfer(BankAccount from, BankAccount to, double amount) {
    // 先检查所有条件
    if (from == null) {
        throw new IllegalArgumentException("源账户不能为空");
    }
    if (to == null) {
        throw new IllegalArgumentException("目标账户不能为空");
    }
    if (amount <= 0) {
        throw new IllegalArgumentException("转账金额必须大于0");
    }

    // 再执行操作
    from.withdraw(amount);
    to.deposit(amount);
}

6. 合理使用检查型和非检查型异常

java
// 检查型异常:可恢复的情况
public void readFile(String path) throws FileNotFoundException {
    // 文件不存在是可以恢复的,用户可以提供新路径
}

// 非检查型异常:编程错误
public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年龄不能为负数");
        // 这是编程错误,不应该捕获
    }
}

常见异常

NullPointerException

java
String str = null;
// str.length();  // NullPointerException

// 预防
if (str != null) {
    System.out.println(str.length());
}

// 使用 Objects.requireNonNull
public void setName(String name) {
    this.name = Objects.requireNonNull(name, "姓名不能为空");
}

// Java 8+ Optional
Optional.ofNullable(str)
    .ifPresent(s -> System.out.println(s.length()));

ArrayIndexOutOfBoundsException

java
int[] arr = new int[5];
// arr[5] = 10;  // ArrayIndexOutOfBoundsException

// 预防
int index = 5;
if (index >= 0 && index < arr.length) {
    arr[index] = 10;
}

ClassCastException

java
Object obj = "Hello";
// Integer num = (Integer) obj;  // ClassCastException

// 预防
if (obj instanceof Integer) {
    Integer num = (Integer) obj;
}

NumberFormatException

java
// Integer.parseInt("abc");  // NumberFormatException

// 预防
String input = "abc";
try {
    int num = Integer.parseInt(input);
} catch (NumberFormatException e) {
    System.out.println("无效的数字格式: " + input);
}

// 使用工具方法
public static Integer parseInteger(String str) {
    try {
        return Integer.parseInt(str);
    } catch (NumberFormatException e) {
        return null;
    }
}

实践示例

输入验证工具

java
import java.util.Objects;

public class ValidationUtils {

    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null) {
            throw new IllegalArgumentException(message);
        }
        return obj;
    }

    public static String requireNonEmpty(String str, String message) {
        requireNonNull(str, message);
        if (str.trim().isEmpty()) {
            throw new IllegalArgumentException(message);
        }
        return str;
    }

    public static int requirePositive(int value, String message) {
        if (value <= 0) {
            throw new IllegalArgumentException(message);
        }
        return value;
    }

    public static int requireRange(int value, int min, int max, String message) {
        if (value < min || value > max) {
            throw new IllegalArgumentException(
                message + " (范围: " + min + "-" + max + ")");
        }
        return value;
    }

    public static String requireEmail(String email) {
        requireNonEmpty(email, "邮箱不能为空");
        if (!email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
        return email;
    }

    public static String requirePhone(String phone) {
        requireNonEmpty(phone, "手机号不能为空");
        if (!phone.matches("^1[3-9]\\d{9}$")) {
            throw new IllegalArgumentException("手机号格式不正确");
        }
        return phone;
    }
}

// 使用
public class User {
    private String name;
    private String email;
    private int age;

    public User(String name, String email, int age) {
        this.name = ValidationUtils.requireNonEmpty(name, "姓名不能为空");
        this.email = ValidationUtils.requireEmail(email);
        this.age = ValidationUtils.requireRange(age, 0, 150, "年龄无效");
    }
}

异常处理框架

java
import java.util.function.*;

public class ExceptionHandler {

    // 处理可能抛出异常的 Supplier
    public static <T> T handle(Supplier<T> supplier, T defaultValue) {
        try {
            return supplier.get();
        } catch (Exception e) {
            return defaultValue;
        }
    }

    // 处理可能抛出异常的 Runnable
    public static void handle(Runnable runnable) {
        try {
            runnable.run();
        } catch (Exception e) {
            // 静默处理
        }
    }

    // 包装异常
    public static <T> T wrap(Supplier<T> supplier,
            Function<Exception, RuntimeException> wrapper) {
        try {
            return supplier.get();
        } catch (Exception e) {
            throw wrapper.apply(e);
        }
    }

    // 重试机制
    public static <T> T retry(Supplier<T> supplier, int maxRetries) {
        Exception lastException = null;
        for (int i = 0; i < maxRetries; i++) {
            try {
                return supplier.get();
            } catch (Exception e) {
                lastException = e;
            }
        }
        throw new RuntimeException("重试" + maxRetries + "次后仍失败", lastException);
    }

    // 使用示例
    public static void main(String[] args) {
        // 安全解析整数
        Integer num = handle(() -> Integer.parseInt("123"), 0);
        System.out.println(num);

        // 重试网络请求
        String result = retry(() -> {
            // 模拟网络请求
            return "success";
        }, 3);
        System.out.println(result);
    }
}

业务异常处理

java
import java.util.*;

// 业务异常基类
public class BusinessException extends RuntimeException {
    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() { return code; }
}

// 具体业务异常
public class UserNotFoundException extends BusinessException {
    public UserNotFoundException(Long userId) {
        super("USER_NOT_FOUND", "用户不存在: " + userId);
    }
}

public class InsufficientBalanceException extends BusinessException {
    public InsufficientBalanceException(double balance, double amount) {
        super("INSUFFICIENT_BALANCE",
            "余额不足: 当前 " + balance + ", 需要 " + amount);
    }
}

// 统一异常处理器
public class GlobalExceptionHandler {

    public Map<String, Object> handle(Exception e) {
        Map<String, Object> result = new HashMap<>();

        if (e instanceof BusinessException) {
            BusinessException be = (BusinessException) e;
            result.put("success", false);
            result.put("code", be.getCode());
            result.put("message", be.getMessage());
        } else {
            result.put("success", false);
            result.put("code", "SYSTEM_ERROR");
            result.put("message", "系统错误");
        }

        return result;
    }
}

// 使用
public class UserService {
    private Map<Long, User> users = new HashMap<>();

    public User getUser(Long userId) {
        User user = users.get(userId);
        if (user == null) {
            throw new UserNotFoundException(userId);
        }
        return user;
    }
}