强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Java 完全指南 / 12 - 异常处理:try/catch/finally、自定义异常、checked/unchecked

12 - 异常处理:try/catch/finally、自定义异常、checked/unchecked

异常体系结构

Throwable
├── Error(严重错误,不捕获)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception(可处理的异常)
    ├── 受检异常(Checked)—— 编译器强制处理
    │   ├── IOException
    │   ├── SQLException
    │   └── ...
    └── RuntimeException(Unchecked)—— 运行时异常
        ├── NullPointerException
        ├── ArrayIndexOutOfBoundsException
        ├── IllegalArgumentException
        ├── ClassCastException
        └── ...

try-catch-finally

import java.io.*;

public class TryCatchDemo {
    public static void main(String[] args) {
        // 基本 try-catch
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }

        // 多重 catch
        try {
            String s = null;
            s.length();  // NullPointerException
        } catch (NullPointerException e) {
            System.out.println("空指针: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("其他异常: " + e.getMessage());
        } finally {
            System.out.println("finally 总是执行");
        }

        // 多异常合并(JDK 7+)
        try {
            int[] arr = new int[5];
            arr[10] = 1;
        } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
            System.out.println("运行时异常: " + e.getClass().getSimpleName());
        }
    }
}

try-with-resources(JDK 7+)

import java.io.*;

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        // 自动关闭实现了 AutoCloseable 的资源
        try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
             BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {

            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
        } catch (IOException e) {
            System.err.println("IO 错误: " + e.getMessage());
        }
        // reader 和 writer 在 try 块结束时自动关闭

        // JDK 9+ 之前声明的资源也可以
        BufferedReader br = new BufferedReader(new StringReader("hello"));
        try (br) {
            System.out.println(br.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

异常用法

public class ExceptionUsageDemo {

    // throws 声明抛出受检异常
    public static String readFile(String path) throws IOException {
        // throw 主动抛出异常
        if (path == null || path.isBlank()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }
        java.nio.file.Files.readString(java.nio.file.Path.of(path));
        return "ok";
    }

    // 异常链 —— 包装原始异常
    public static void processOrder(String orderId) {
        try {
            if (orderId == null) throw new RuntimeException("订单ID为空");
        } catch (RuntimeException e) {
            throw new OrderProcessingException("处理订单失败: " + orderId, e);
        }
    }

    // 异常信息增强
    public static int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException(
                String.format("除数不能为零: %d / %d", a, b));
        }
        return a / b;
    }

    public static void main(String[] args) {
        // 异常信息
        try {
            divide(10, 0);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            for (StackTraceElement el : e.getStackTrace()) {
                System.out.println("  at " + el);
            }
        }
    }
}

// 自定义受检异常
class OrderProcessingException extends Exception {
    public OrderProcessingException(String message) {
        super(message);
    }
    public OrderProcessingException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 自定义非受检异常
class BusinessException extends RuntimeException {
    private final String code;
    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
    public String getCode() { return code; }
}

Checked vs Unchecked

维度Checked 异常Unchecked 异常
基类Exception(非 RuntimeException)RuntimeException
编译检查✅ 必须处理❌ 不强制
处理方式try-catchthrows可以不处理
典型IOException, SQLExceptionNPE, IAE
设计意图可恢复的错误编程错误

何时使用哪种异常

// ✅ Checked —— 调用者可以且应该处理
public class FileService {
    public String readConfig(String path) throws IOException {
        // 文件可能不存在,这是正常的可恢复情况
        return Files.readString(Path.of(path));
    }
}

// ✅ Unchecked —— 编程错误,调用者不应捕获
public class UserService {
    public void createUser(String name) {
        if (name == null) {
            throw new NullPointerException("name");      // 编程错误
        }
        if (name.isBlank()) {
            throw new IllegalArgumentException("name");  // 参数校验失败
        }
    }
}

异常处理最佳实践

public class ExceptionBestPractices {

    // ❌ 不要捕获 Exception 或 Throwable
    // void bad() { try { ... } catch (Exception e) { } }

    // ❌ 不要吞掉异常
    // void bad2() { try { ... } catch (Exception e) { /* 什么都不做 */ } }

    // ✅ 精确捕获
    void good() {
        try {
            riskyOperation();
        } catch (FileNotFoundException e) {
            log.warn("配置文件不存在,使用默认配置");
        } catch (IOException e) {
            log.error("读取文件失败", e);
            throw new ServiceException("服务不可用", e);
        }
    }

    // ✅ try-with-resources 确保资源关闭
    void copyFile(String src, String dst) throws IOException {
        try (var in = new FileInputStream(src);
             var out = new FileOutputStream(dst)) {
            in.transferTo(out);
        }
    }

    // ✅ 使用 Optional 代替返回 null
    java.util.Optional<String> findUser(String id) {
        return java.util.Optional.ofNullable(getFromDB(id));
    }
}

⚠️ 注意事项

  1. 不要用异常控制流程 — 异常开销大,应用于异常情况。
  2. catch 块要有意义 — 至少记录日志,不要吞掉异常。
  3. finally 不要使用 return — 会覆盖 try/catch 中的 return 值。
  4. 不要在 finally 中抛异常 — 会覆盖 try 中的异常。

💡 技巧

  1. 全局异常处理器(Spring Boot):

    @RestControllerAdvice
    class GlobalExceptionHandler {
        @ExceptionHandler(BusinessException.class)
        public ResponseEntity<?> handleBiz(BusinessException e) {
            return ResponseEntity.badRequest().body(Map.of("code", e.getCode(), "msg", e.getMessage()));
        }
    }
    
  2. Supplier 延迟构造异常(避免不必要的堆栈计算):

    static <T> T requireNonNull(T obj, Supplier<String> msg) {
        if (obj == null) throw new NullPointerException(msg.get());
        return obj;
    }
    

🏢 业务场景

  • Web 服务: 统一异常处理返回标准错误 JSON。
  • 数据库操作: SQLException 是 checked,需要 try-with-resources 关闭连接。
  • 文件处理: IOException 处理文件不存在、权限不足等情况。
  • 微服务: 熔断降级时捕获远程调用异常,返回兜底数据。

📖 扩展阅读