22 异常处理——从入门到精通的完整指南

目录

🟡 22 异常处理------从入门到精通的完整指南

更新日期 :2026年6月 | Java入门到精通系列 · 第三阶段·核心进阶

© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。



一、异常处理概述

1.1 什么是异常?

异常(Exception)是程序在运行过程中发生的、打断正常执行流程的事件。Java使用异常处理机制来优雅地处理这些情况。

java 复制代码
// 没有异常处理------程序崩溃
public class NoExceptionHandling {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println(arr[5]);  // ArrayIndexOutOfBoundsException!
        System.out.println("这行永远不会执行");
    }
}

// 有异常处理------程序继续运行
public class WithExceptionHandling {
    public static void main(String[] args) {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界:" + e.getMessage());
        }
        System.out.println("程序继续运行");
    }
}

1.2 为什么需要异常处理?

没有异常处理 有异常处理
程序崩溃 程序优雅降级
用户看到错误信息 友好的错误提示
资源泄漏 资源正确释放
难以调试 详细的错误栈信息

二、Java异常体系结构

2.1 异常类层次结构

复制代码
Throwable
├── Error(错误,不应捕获)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── NoClassDefFoundError
│
└── Exception(异常)
    ├── RuntimeException(运行时异常/非受检异常)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ClassCastException
    │   ├── IllegalArgumentException
    │   ├── NumberFormatException
    │   └── ArithmeticException
    │
    └── 受检异常(Checked Exception)
        ├── IOException
        ├── SQLException
        ├── FileNotFoundException
        ├── ClassNotFoundException
        └── InterruptedException

2.2 Error vs Exception

特征 Error Exception
严重程度 致命错误 可恢复错误
是否捕获 不建议 应该捕获
示例 OutOfMemoryError IOException
原因 JVM级别问题 程序逻辑问题

2.3 受检异常 vs 非受检异常

特征 受检异常 非受检异常
编译器检查 必须处理 不强制处理
继承关系 Exception子类(非RuntimeException) RuntimeException子类
典型场景 IO操作、数据库操作 空指针、数组越界
处理方式 try-catch或throws 可不处理(但建议处理)
java 复制代码
// 受检异常:编译器强制要求处理
public void readFile() {
    // 不处理编译报错:Unhandled exception: java.io.FileNotFoundException
    FileInputStream fis = new FileInputStream("test.txt");
}

// 必须处理方式1:try-catch
public void readFile1() {
    try {
        FileInputStream fis = new FileInputStream("test.txt");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

// 必须处理方式2:throws声明
public void readFile2() throws FileNotFoundException {
    FileInputStream fis = new FileInputStream("test.txt");
}

// 非受检异常:编译器不强制处理
public void divide() {
    int result = 10 / 0;  // ArithmeticException,但编译通过
}

三、try-catch-finally详解

3.1 基本语法

java 复制代码
try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理ExceptionType1
} catch (ExceptionType2 e2) {
    // 处理ExceptionType2
} finally {
    // 无论是否发生异常,都会执行
    // 通常用于资源清理
}

3.2 多重catch

java 复制代码
public void processInput(String input) {
    try {
        int number = Integer.parseInt(input);
        int result = 100 / number;
        int[] arr = new int[3];
        arr[5] = result;
    } catch (NumberFormatException e) {
        System.out.println("数字格式错误:" + e.getMessage());
    } catch (ArithmeticException e) {
        System.out.println("算术错误:" + e.getMessage());
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("数组越界:" + e.getMessage());
    } catch (Exception e) {
        // 兜底捕获
        System.out.println("未知错误:" + e.getMessage());
    }
}

3.3 多异常捕获(Java 7+)

java 复制代码
// Java 7引入的多异常捕获语法
public void processFile(String path) {
    try {
        // 文件操作
    } catch (FileNotFoundException | SecurityException e) {
        // 注意:多个异常类型用 | 分隔
        // 变量e隐式为final,不能重新赋值
        System.out.println("无法访问文件:" + e.getMessage());
    }
}

3.4 finally的特殊行为

java 复制代码
public class FinallyBehavior {
    // finally在return之前执行
    public int testFinally() {
        try {
            return 1;
        } finally {
            System.out.println("finally执行了!");
            // return 2;  // 不建议在finally中return!会覆盖try中的return
        }
        // 输出:finally执行了!,返回值为1
    }

    // finally在异常传播之前执行
    public void testFinallyWithException() {
        try {
            int x = 1 / 0;
        } catch (ArithmeticException e) {
            System.out.println("catch: " + e.getMessage());
            throw new RuntimeException("包装异常");
        } finally {
            System.out.println("finally执行了!");
        }
    }

    // 特殊情况:finally不执行的情况
    public void testFinallyNotExecute() {
        try {
            System.out.println("try");
            System.exit(0);  // JVM退出,finally不会执行
        } finally {
            System.out.println("永远不会执行!");
        }
    }
}

3.5 异常信息的获取

java 复制代码
try {
    // 可能抛出异常的代码
} catch (Exception e) {
    // 获取异常信息
    System.out.println("getMessage(): " + e.getMessage());
    System.out.println("toString(): " + e.toString());
    e.printStackTrace();  // 打印完整堆栈

    // 获取异常原因
    Throwable cause = e.getCause();
    if (cause != null) {
        System.out.println("原因:" + cause.getMessage());
    }

    // Java 9+:获取堆栈帧
    StackTraceElement[] stackTrace = e.getStackTrace();
    for (StackTraceElement element : stackTrace) {
        System.out.println("  at " + element);
    }
}

四、throws与throw

4.1 throws------声明异常

java 复制代码
public class ThrowsDemo {
    // 声明方法可能抛出的受检异常
    public static String readConfig(String path) throws IOException, SecurityException {
        // 如果path为null,抛出非受检异常
        if (path == null) {
            throw new IllegalArgumentException("路径不能为null");
        }

        // 受检异常必须声明或捕获
        BufferedReader reader = new BufferedReader(new FileReader(path));
        String config = reader.readLine();
        reader.close();
        return config;
    }

    public static void main(String[] args) {
        try {
            String config = readConfig("config.txt");
        } catch (IOException e) {
            System.err.println("IO错误:" + e.getMessage());
        } catch (SecurityException e) {
            System.err.println("权限不足:" + e.getMessage());
        }
    }
}

4.2 throw------抛出异常

java 复制代码
public class ThrowDemo {
    public static void validateAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数:" + age);
        }
        if (age > 150) {
            throw new IllegalArgumentException("年龄不合理:" + age);
        }
        System.out.println("年龄有效:" + age);
    }

    // 在业务逻辑中抛出自定义异常
    public static void transfer(Account from, Account to, double amount)
            throws InsufficientBalanceException {
        if (amount <= 0) {
            throw new IllegalArgumentException("转账金额必须为正数");
        }
        if (from.getBalance() < amount) {
            throw new InsufficientBalanceException("余额不足", from.getBalance(), amount);
        }
        from.withdraw(amount);
        to.deposit(amount);
    }
}

4.3 throws vs throw

特征 throws throw
位置 方法签名后面 方法体内
作用 声明方法可能抛出的异常 实际抛出一个异常实例
数量 可以声明多个异常类型 一次只能抛出一个异常
关系 声明契约 执行动作

五、自定义异常

5.1 创建自定义异常

java 复制代码
// 自定义受检异常
public class BusinessException extends Exception {
    private final String errorCode;

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

    public BusinessException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

// 自定义运行时异常
public class DataValidationException extends RuntimeException {
    private final String field;
    private final Object value;

    public DataValidationException(String field, Object value, String message) {
        super(message);
        this.field = field;
        this.value = value;
    }

    public String getField() { return field; }
    public Object getValue() { return value; }
}

// 业务异常层次结构
public class OrderException extends BusinessException {
    public OrderException(String errorCode, String message) {
        super(errorCode, message);
    }
}

public class OrderNotFoundException extends OrderException {
    private final String orderId;

    public OrderNotFoundException(String orderId) {
        super("ORDER_NOT_FOUND", "订单不存在:" + orderId);
        this.orderId = orderId;
    }

    public String getOrderId() { return orderId; }
}

public class InsufficientStockException extends OrderException {
    private final String productId;
    private final int requested;
    private final int available;

    public InsufficientStockException(String productId, int requested, int available) {
        super("INSUFFICIENT_STOCK",
                String.format("库存不足: 商品[%s] 需要%d件,仅剩%d件", productId, requested, available));
        this.productId = productId;
        this.requested = requested;
        this.available = available;
    }
}

5.2 使用自定义异常

java 复制代码
public class OrderService {
    private Map<String, Order> orders = new HashMap<>();
    private Map<String, Integer> stock = new HashMap<>();

    public Order createOrder(String userId, String productId, int quantity)
            throws OrderException {

        // 验证库存
        Integer available = stock.get(productId);
        if (available == null || available < quantity) {
            throw new InsufficientStockException(
                    productId, quantity, available != null ? available : 0);
        }

        // 创建订单
        Order order = new Order(userId, productId, quantity);
        orders.put(order.getId(), order);
        stock.merge(productId, quantity, (a, b) -> a - b);

        return order;
    }

    public Order getOrder(String orderId) throws OrderNotFoundException {
        Order order = orders.get(orderId);
        if (order == null) {
            throw new OrderNotFoundException(orderId);
        }
        return order;
    }
}

六、try-with-resources(Java 7+)

6.1 基本用法

java 复制代码
import java.io.*;

public class TryWithResourcesDemo {
    // 传统方式(繁琐,容易遗漏关闭)
    public String readFileOld(String path) throws IOException {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(path));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        } finally {
            if (reader != null) {
                reader.close();  // 这里也可能抛出异常!
            }
        }
    }

    // try-with-resources(简洁安全)
    public String readFileNew(String path) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        }
        // reader自动关闭,无需手动处理
    }

    // 多个资源
    public void copyFile(String src, String dest) throws IOException {
        try (
            InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dest)
        ) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        }
        // in和out都自动关闭
    }
}

6.2 自定义AutoCloseable资源

java 复制代码
public class DatabaseConnection implements AutoCloseable {
    private String url;
    private boolean closed = false;

    public DatabaseConnection(String url) {
        this.url = url;
        System.out.println("连接数据库:" + url);
    }

    public void execute(String sql) {
        if (closed) {
            throw new IllegalStateException("连接已关闭");
        }
        System.out.println("执行SQL:" + sql);
    }

    @Override
    public void close() {
        if (!closed) {
            System.out.println("关闭数据库连接:" + url);
            closed = true;
        }
    }

    // 使用示例
    public static void main(String[] args) {
        try (DatabaseConnection conn = new DatabaseConnection("jdbc:mysql://localhost:3306/test")) {
            conn.execute("SELECT * FROM users");
            conn.execute("INSERT INTO logs VALUES (...)");
        }
        // 自动调用conn.close()
    }
}

6.3 异常抑制(Suppressed Exception)

java 复制代码
public class SuppressedExceptionDemo {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            resource.doWork();
            throw new RuntimeException("主异常");
        } catch (RuntimeException e) {
            System.out.println("主异常:" + e.getMessage());

            // 获取被抑制的异常
            Throwable[] suppressed = e.getSuppressed();
            for (Throwable t : suppressed) {
                System.out.println("被抑制的异常:" + t.getMessage());
            }
        }
    }

    static class MyResource implements AutoCloseable {
        void doWork() {
            System.out.println("工作中...");
        }

        @Override
        public void close() {
            System.out.println("资源关闭时抛出异常");
            throw new RuntimeException("关闭异常");
        }
    }
}

七、异常处理最佳实践

7.1 ✅ 应该做的

java 复制代码
// 1. 具体的异常类型优于宽泛的Exception
try {
    // 业务逻辑
} catch (FileNotFoundException e) {
    // 处理文件不存在
} catch (IOException e) {
    // 处理其他IO异常
}

// 2. 尽早失败(Fail-Fast)
public void processOrder(Order order) {
    Objects.requireNonNull(order, "订单不能为null");
    if (order.getAmount() <= 0) {
        throw new IllegalArgumentException("订单金额必须为正数");
    }
    // 继续处理...
}

// 3. 用异常传递错误信息
public class ServiceException extends RuntimeException {
    private final String errorCode;
    private final Map<String, Object> context;

    public ServiceException(String errorCode, String message, Map<String, Object> context) {
        super(message);
        this.errorCode = errorCode;
        this.context = context != null ? context : Collections.emptyMap();
    }

    // 提供有用的调试信息
    @Override
    public String toString() {
        return String.format("[%s] %s, context=%s", errorCode, getMessage(), context);
    }
}

// 4. 在合适的层级处理异常
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/orders")
    public ApiResponse<Order> createOrder(@RequestBody CreateOrderRequest request) {
        try {
            Order order = orderService.createOrder(request);
            return ApiResponse.ok(order);
        } catch (InsufficientStockException e) {
            // 业务异常:返回400
            return ApiResponse.badRequest(e.getMessage());
        } catch (Exception e) {
            // 未知异常:记录日志,返回500
            log.error("创建订单失败", e);
            return ApiResponse.error("系统繁忙,请稍后重试");
        }
    }
}

7.2 ❌ 不应该做的

java 复制代码
// 1. 不要吞掉异常
try {
    // 业务逻辑
} catch (Exception e) {
    // ❌ 什么都不做,异常被吞掉
}

// 2. 不要捕获了又原样抛出
try {
    // 业务逻辑
} catch (Exception e) {
    throw e;  // ❌ 捕获了又原样抛出,没有任何意义
}

// 3. 不要用异常控制流程
// ❌ 不好的做法
try {
    int result = map.get(key);
} catch (NullPointerException e) {
    result = defaultValue;
}

// ✅ 好的做法
int result = map.getOrDefault(key, defaultValue);

// 4. 不要捕获Throwable或Error
try {
    // 业务逻辑
} catch (Throwable e) {  // ❌ 会捕获Error
    // ...
}

// 5. 不要在finally中return
public int badMethod() {
    try {
        return 1;
    } finally {
        return 2;  // ❌ 会覆盖try中的return
    }
}

7.3 日志记录最佳实践

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingBestPractice {
    private static final Logger log = LoggerFactory.getLogger(LoggingBestPractice.class);

    public void process() {
        try {
            // 业务逻辑
        } catch (BusinessException e) {
            // 业务异常:WARN级别
            log.warn("业务异常: code={}, message={}", e.getErrorCode(), e.getMessage());
            throw e;
        } catch (Exception e) {
            // 系统异常:ERROR级别,记录完整堆栈
            log.error("系统异常: {}", e.getMessage(), e);
            throw new SystemException("系统处理失败", e);
        }
    }
}

八、异常处理设计模式

8.1 异常转换模式

java 复制代码
// 底层异常转换为业务异常
public class UserRepository {
    public User findById(String id) throws DataAccessException {
        try {
            // 数据库操作
            return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", id);
        } catch (SQLException e) {
            // 将底层异常转换为业务异常
            throw new DataAccessException("查询用户失败: " + id, e);
        }
    }
}

// 各层异常转换
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User getUser(String id) throws UserNotFoundException {
        try {
            User user = userRepository.findById(id);
            if (user == null) {
                throw new UserNotFoundException(id);
            }
            return user;
        } catch (DataAccessException e) {
            throw new ServiceException("USER_QUERY_FAILED", "查询用户失败", e);
        }
    }
}

8.2 结果对象模式

java 复制代码
// 用Result对象替代异常(适合高频调用的场景)
public class Result<T> {
    private final boolean success;
    private final T data;
    private final String errorCode;
    private final String errorMessage;

    private Result(boolean success, T data, String errorCode, String errorMessage) {
        this.success = success;
        this.data = data;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public static <T> Result<T> ok(T data) {
        return new Result<>(true, data, null, null);
    }

    public static <T> Result<T> fail(String errorCode, String message) {
        return new Result<>(false, null, errorCode, message);
    }

    public boolean isSuccess() { return success; }
    public T getData() { return data; }
    public String getErrorCode() { return errorCode; }
    public String getErrorMessage() { return errorMessage; }

    public T orElse(T defaultValue) {
        return success ? data : defaultValue;
    }

    public T orElseThrow() {
        if (!success) {
            throw new BusinessException(errorCode, errorMessage);
        }
        return data;
    }
}

// 使用示例
public class UserService {
    public Result<User> findUser(String id) {
        User user = userRepository.findById(id);
        if (user == null) {
            return Result.fail("USER_NOT_FOUND", "用户不存在: " + id);
        }
        return Result.ok(user);
    }
}

// 调用方
Result<User> result = userService.findUser("123");
if (result.isSuccess()) {
    User user = result.getData();
} else {
    System.out.println(result.getErrorMessage());
}

九、常见面试题解析

面试题1:Error和Exception的区别?

:Error是程序无法处理的严重错误(如OutOfMemoryError),通常由JVM抛出,不应该被捕获。Exception是程序可以处理的异常,分为受检异常(必须处理)和运行时异常(可以不处理)。

面试题2:final、finally、finalize的区别?

关键字 作用 使用场景
final 修饰类(不可继承)、方法(不可重写)、变量(不可修改) 定义常量、锁定实现
finally try-catch-finally中的最终执行块 资源清理
finalize Object的方法,在GC前调用(已废弃) 不推荐使用

面试题3:try-with-resources的原理?

java 复制代码
// 编译器会将try-with-resources转换为:
AutoCloseable resource = null;
try {
    resource = createResource();
    // 使用resource
} catch (Throwable t) {
    // 处理异常
} finally {
    if (resource != null) {
        resource.close();
    }
}

面试题4:如何设计一个好的异常体系?

  1. 区分业务异常和技术异常
  2. 提供错误码和友好消息
  3. 包含上下文信息
  4. 合理的异常层次结构
  5. 在合适的层级处理异常

十、总结与下篇预告

本篇核心要点

要点 说明
异常体系 Error vs Exception,受检 vs 非受检
try-catch-finally 多重catch,finally总执行
throws vs throw 声明异常 vs 抛出异常
自定义异常 继承Exception或RuntimeException
try-with-resources AutoCloseable自动关闭资源
最佳实践 不要吞异常,日志记录,异常转换

🤔 互动问题

  1. 为什么Java要求受检异常必须处理,而运行时异常不需要?
  2. 在Spring项目中,异常处理有哪些最佳实践?
  3. Result模式和异常模式各自适合什么场景?

📖 下篇预告

下一篇我们将学习**《泛型》**,深入了解泛型类、泛型方法、泛型接口、通配符以及类型擦除的概念。


参考资料

相关推荐
小旭95271 小时前
Spring AI Alibaba 从入门到实战:一站式掌握企业级 AI 应用开发
java·人工智能·spring
Arrom2 小时前
DLNA 渲染端排障实战:从 20s 卡顿到 stale subscriber 的两周追凶之旅
android·java
J-Tony112 小时前
【JVM】三色标记法
java·jvm·算法
李白的天不白2 小时前
docker ps
java
NE_STOP3 小时前
Docker--Docker Swarm集群
java
两年半的个人练习生^_^3 小时前
JMM 进阶:彻底理解 CAS 实现原理
java·开发语言
wuminyu3 小时前
Java锁机制之park和unpark源码剖析
java·linux·c语言·jvm·c++
W_LuYi1853 小时前
手撸极简zkEVM验证器:RISC-V电路实践
java·risc-v
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题 第102题】【并发篇】第2题:volatile 能否保证线程安全?
java·安全·面试