目录
- [🟡 22 异常处理------从入门到精通的完整指南](#🟡 22 异常处理——从入门到精通的完整指南)
-
- 一、异常处理概述
-
- [1.1 什么是异常?](#1.1 什么是异常?)
- [1.2 为什么需要异常处理?](#1.2 为什么需要异常处理?)
- 二、Java异常体系结构
-
- [2.1 异常类层次结构](#2.1 异常类层次结构)
- [2.2 Error vs Exception](#2.2 Error vs Exception)
- [2.3 受检异常 vs 非受检异常](#2.3 受检异常 vs 非受检异常)
- 三、try-catch-finally详解
-
- [3.1 基本语法](#3.1 基本语法)
- [3.2 多重catch](#3.2 多重catch)
- [3.3 多异常捕获(Java 7+)](#3.3 多异常捕获(Java 7+))
- [3.4 finally的特殊行为](#3.4 finally的特殊行为)
- [3.5 异常信息的获取](#3.5 异常信息的获取)
- 四、throws与throw
-
- [4.1 throws------声明异常](#4.1 throws——声明异常)
- [4.2 throw------抛出异常](#4.2 throw——抛出异常)
- [4.3 throws vs throw](#4.3 throws vs throw)
- 五、自定义异常
-
- [5.1 创建自定义异常](#5.1 创建自定义异常)
- [5.2 使用自定义异常](#5.2 使用自定义异常)
- [六、try-with-resources(Java 7+)](#六、try-with-resources(Java 7+))
-
- [6.1 基本用法](#6.1 基本用法)
- [6.2 自定义AutoCloseable资源](#6.2 自定义AutoCloseable资源)
- [6.3 异常抑制(Suppressed Exception)](#6.3 异常抑制(Suppressed Exception))
- 七、异常处理最佳实践
-
- [7.1 ✅ 应该做的](#7.1 ✅ 应该做的)
- [7.2 ❌ 不应该做的](#7.2 ❌ 不应该做的)
- [7.3 日志记录最佳实践](#7.3 日志记录最佳实践)
- 八、异常处理设计模式
-
- [8.1 异常转换模式](#8.1 异常转换模式)
- [8.2 结果对象模式](#8.2 结果对象模式)
- 九、常见面试题解析
-
- 十、总结与下篇预告
-
- 本篇核心要点
- [🤔 互动问题](#🤔 互动问题)
- [📖 下篇预告](#📖 下篇预告)
- 参考资料
🟡 22 异常处理------从入门到精通的完整指南
更新日期 :2026年6月 | Java入门到精通系列 · 第三阶段·核心进阶
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。
一、异常处理概述
1.1 什么是异常?
异常(Exception)是程序在运行过程中发生的、打断正常执行流程的事件。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 |
可不处理(但建议处理) |
// 受检异常:编译器强制要求处理
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 基本语法
try {
// 可能抛出异常的代码
} catch (ExceptionType1 e1) {
// 处理ExceptionType1
} catch (ExceptionType2 e2) {
// 处理ExceptionType2
} finally {
// 无论是否发生异常,都会执行
// 通常用于资源清理
}
3.2 多重catch
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 7引入的多异常捕获语法
public void processFile(String path) {
try {
// 文件操作
} catch (FileNotFoundException | SecurityException e) {
// 注意:多个异常类型用 | 分隔
// 变量e隐式为final,不能重新赋值
System.out.println("无法访问文件:" + e.getMessage());
}
}
3.4 finally的特殊行为
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 异常信息的获取
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------声明异常
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------抛出异常
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 创建自定义异常
// 自定义受检异常
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 使用自定义异常
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 基本用法
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资源
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)
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 ✅ 应该做的
// 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 ❌ 不应该做的
// 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 日志记录最佳实践
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 异常转换模式
// 底层异常转换为业务异常
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 结果对象模式
// 用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的原理?
// 编译器会将try-with-resources转换为:
AutoCloseable resource = null;
try {
resource = createResource();
// 使用resource
} catch (Throwable t) {
// 处理异常
} finally {
if (resource != null) {
resource.close();
}
}
面试题4:如何设计一个好的异常体系?
- 区分业务异常和技术异常
- 提供错误码和友好消息
- 包含上下文信息
- 合理的异常层次结构
- 在合适的层级处理异常
十、总结与下篇预告
本篇核心要点
| 要点 |
说明 |
| 异常体系 |
Error vs Exception,受检 vs 非受检 |
| try-catch-finally |
多重catch,finally总执行 |
| throws vs throw |
声明异常 vs 抛出异常 |
| 自定义异常 |
继承Exception或RuntimeException |
| try-with-resources |
AutoCloseable自动关闭资源 |
| 最佳实践 |
不要吞异常,日志记录,异常转换 |
🤔 互动问题
- 为什么Java要求受检异常必须处理,而运行时异常不需要?
- 在Spring项目中,异常处理有哪些最佳实践?
- Result模式和异常模式各自适合什么场景?
📖 下篇预告
下一篇我们将学习**《泛型》**,深入了解泛型类、泛型方法、泛型接口、通配符以及类型擦除的概念。
参考资料