异常处理机制
异常处理机制
**异常(Exception)**是程序运行过程中出现的非正常情况。Java 提供了完整的异常处理机制,让程序能够在出错时优雅地做出反应,而不是直接崩溃。本文将系统讲解 Java 异常体系、常用处理语法以及自定义异常的实践。
Java 异常体系结构
Java 的异常体系以 Throwable 为顶层父类,分为两大分支:
Error(错误):JVM 本身发生的严重问题,如内存溢出(OutOfMemoryError)、栈溢出(StackOverflowError)。程序无法从 Error 中恢复,不应捕获 Error。Exception(异常):程序运行中可预见的问题,如空指针访问、数组越界、网络中断等。其中RuntimeException及其子类称为非受检异常(Unchecked Exception),编译器不强制要求处理;其他 Exception 及其子类称为受检异常(Checked Exception),必须在代码中显式处理。
Throwable
├── Error(错误)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception(异常)
├── RuntimeException(非受检异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── IllegalArgumentException
└── IOException / SQLException 等(受检异常)try-catch-finally:基本异常捕获
基础语法
try {
// 可能抛出异常的代码
int result = 10 / 0; // ArithmeticException:除数不能为零
} catch (ArithmeticException e) {
// 捕获特定异常类型
System.out.println("发生算术异常:" + e.getMessage());
} catch (Exception e) {
// 捕获更通用的异常(兜底)
System.out.println("发生未知异常:" + e.getMessage());
} finally {
// 无论是否发生异常,finally 块一定会执行
System.out.println("清理资源...");
}执行顺序:try 中的代码从左到右执行——若发生异常,控制权跳转到匹配的 catch;若未发生异常,跳过所有 catch,直接执行 finally。
多异常捕获(Java 7+)
try {
int index = Integer.parseInt(input);
int value = arr[index];
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
// Java 7+ 支持在一个 catch 中捕获多种异常类型
System.out.println("输入无效:" + e.getMessage());
}finally 的实际用途
finally 块最适合做资源释放——关闭文件流、数据库连接、网络连接等:
import java.io.*;
FileInputStream fis = null;
try {
fis = new FileInputStream("data.txt");
byte[] buffer = new byte[1024];
int len = fis.read(buffer);
System.out.println("读取到 " + len + " 字节");
} catch (IOException e) {
System.out.println("读取失败:" + e.getMessage());
} finally {
// 即使发生异常,也要确保资源被关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}不过在 Java 7+ 中,推荐使用 try-with-resources 自动管理资源。
try-with-resources(自动资源管理)
try-with-resources 是 Java 7 引入的语法,声明了实现 AutoCloseable 接口的资源后,代码块结束时自动调用 close() 方法,无需手动编写 finally:
// 传统写法:需要手动关闭,代码繁琐
try {
// ...
} finally {
if (resource != null) {
resource.close();
}
}
// try-with-resources:自动关闭,简洁安全
try (FileInputStream fis = new FileInputStream("data.txt")) {
byte[] buffer = new byte[1024];
int len = fis.read(buffer);
System.out.println("读取到 " + len + " 字节");
} catch (IOException e) {
System.out.println("读取失败:" + e.getMessage());
}
// fis 在此处自动关闭,即使发生异常也会执行// 同时管理多个资源
try (
FileInputStream fis = new FileInputStream("data.txt");
FileOutputStream fos = new FileOutputStream("copy.txt")
) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件复制完成");
} catch (IOException e) {
System.out.println("IO 错误:" + e.getMessage());
}throws 与 throw:异常声明与抛出
throws:声明可能抛出的异常
方法签名中使用 throws 关键字声明该方法可能抛出的异常类型,将异常处理的责任交给调用方:
// 声明多个受检异常
public void readFile(String path) throws IOException, FileNotFoundException {
FileReader reader = new FileReader(path); // 可能抛出 FileNotFoundException
BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
br.close();
}throw:手动抛出异常
使用 throw 关键字主动抛出一个异常实例,通常配合条件判断使用:
public double divide(double a, double b) {
if (b == 0) {
// 手动抛出 ArithmeticException
throw new ArithmeticException("除数不能为零");
}
return a / b;
}// 调用方捕获异常
try {
double result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage()); // 除数不能为零
}
throwvsthrows:throw是动词”抛出”,用于生成并抛出异常对象;throws是副词”声明”,用于方法签名中声明可能抛出的异常类型。
自定义异常
当 Java 内置异常无法精确描述业务错误时,可以定义自己的异常类:
// 继承 RuntimeException → 非受检异常(业务异常常用)
public class BusinessException extends RuntimeException {
private int errorCode; // 业务错误码
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
}public class UserService {
public User login(String username, String password) {
if (username == null || username.trim().isEmpty()) {
throw new BusinessException(1001, "用户名不能为空");
}
if (password == null || password.length() < 6) {
throw new BusinessException(1002, "密码长度不能少于6位");
}
// 登录逻辑...
return user;
}
}异常处理的最佳实践
1. 避免空的 catch 块
// ❌ 错误:异常被静默忽略,问题难以排查
try {
doSomething();
} catch (Exception e) {
// 什么都不做
}
// ✅ 正确:至少记录异常信息
try {
doSomething();
} catch (Exception e) {
LOGGER.warning("操作失败: " + e.getMessage());
// 或重新抛出,提供更多上下文
throw new RuntimeException("业务操作失败", e);
}2. 异常链(异常传递)
捕获低层异常后重新抛出高层异常,保留原始异常信息:
try {
dao.findUserById(id);
} catch (SQLException e) {
// 保留原始异常原因,避免信息丢失
throw new BusinessException("查询用户失败", e);
}3. 合理选择受检 vs 非受检异常
- 受检异常(checked):外部资源问题(IO、数据库、网络),调用方必须处理——适用于可恢复的业务场景
- 非受检异常(unchecked):编程错误(空指针、参数非法)、不可恢复的系统错误——这类异常通常不需要声明
throws
小结
- Java 异常分为
Error(无法恢复)和Exception(可处理),其中RuntimeException为非受检异常 try-catch-finally是基本异常捕获语法;try-with-resources(Java 7+)是更优雅的资源管理方式throws在方法签名中声明异常,throw在代码中主动抛出异常- 自定义异常应继承
RuntimeException(业务异常)或Exception(需要调用方处理) - 避免空的 catch 块,合理使用异常链传递上下文信息
掌握异常处理后,程序的健壮性将大幅提升。下一节我们将学习 Java 17+ 的新特性,这些新语法能显著提升代码的表达力和安全性。
评论
Written by
AI-Writer
Related Articles
JVM 原理与调优基础
深入讲解 JVM 内存结构(堆/栈/方法区)、类加载机制、垃圾回收算法(GC),以及 Java 17+ G1GC、ZGC、常用调优参数和诊断工具
Read More后端开发工具链:构建、测试与日志
快速上手 Maven(pom.xml、依赖管理、生命周期)和 Gradle,构建自动化项目;掌握 JUnit 5 单元测试基础和 SLF4J + Logback 日志框架
Read More