一、引言:为何异常处理如此重要?
在任何一门编程语言中,异常处理都是保证程序健壮性和可维护性的关键机制。在 Java 中,异常机制不仅是错误检测工具,更是一种结构化的编程思维方式。
核心问题:
- 什么是异常?Java 中有哪些类型?
- 如何正确抛出和捕获异常?
- 如何设计自定义异常?
- 异常链、finally 机制、try-with-resources 如何运作?
本文将带你系统梳理 Java 的异常体系与使用方法,并配合大量代码实例与图示,掌握异常处理的精髓。
二、Java 异常的层级结构图
+---------------------+
| Throwable |
+---------------------+
/ \
+--------+ +--------+
| Error | | Exception |
+--------+ +--------+
/ \
+----------+ +------------+
| Checked Exception | Unchecked (Runtime) |
+-------------------+----------------------+
✅ 说明:
- Throwable:所有可抛出的类的顶层父类。
- Error:严重错误(如内存溢出)不能被程序处理。
- Exception:可恢复问题,程序应捕获和处理。
三、异常分类与示例
3.1 Checked Exception(受检异常)
编译器强制处理(try-catch 或 throws)
public void readFile() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
}
常见类:
IOException
SQLException
ParseException
3.2 Unchecked Exception(运行时异常)
编译器不会强制处理,但应当避免
int[] arr = new int[3];
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
常见类:
NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException
四、try-catch-finally 使用详解
4.1 基本语法
try {
int x = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("除以零错误:" + e.getMessage());
} finally {
System.out.println("无论是否异常都会执行");
}
4.2 多 catch 块处理
try {
String s = null;
s.length();
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (Exception e) {
System.out.println("其他异常");
}
💡 多个 catch 从具体到通用排列。
五、异常信息查看与日志记录
5.1 使用 e.printStackTrace()
打印堆栈信息
try {
FileInputStream fis = new FileInputStream("non_exist.txt");
} catch (IOException e) {
e.printStackTrace(); // 方便调试和定位
}
5.2 使用日志记录异常
Logger logger = Logger.getLogger(MyClass.class.getName());
try {
int[] nums = new int[5];
nums[10] = 1;
} catch (Exception e) {
logger.log(Level.SEVERE, "数组越界异常", e);
}
六、自定义异常类
6.1 定义一个业务异常
public class MyBusinessException extends Exception {
public MyBusinessException(String message) {
super(message);
}
}
6.2 抛出自定义异常
public void checkAge(int age) throws MyBusinessException {
if (age < 18) {
throw new MyBusinessException("年龄不能小于18岁!");
}
}
七、异常链:保留原始异常信息
try {
throw new IOException("IO错误");
} catch (IOException e) {
throw new RuntimeException("封装异常", e);
}
获取原始异常
catch (RuntimeException e) {
Throwable cause = e.getCause(); // 原始 IOException
}
八、try-with-resources 自动关闭资源(Java 7+)
自动调用 close()
,无需手动写 finally
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
原理:
AutoCloseable
接口:
public interface AutoCloseable {
void close() throws Exception;
}
九、最佳实践建议
场景 | 推荐做法 |
不可恢复异常(如内存) | 不处理,允许程序中止 |
业务逻辑错误 | 定义业务异常类,抛出并由调用者处理 |
重复代码抛异常 | 封装到工具类或统一异常处理模块 |
打印异常信息 | 使用日志框架(如 Log4j、SLF4J) |
流资源关闭 | 使用 try-with-resources |
十、实战案例:学生信息系统中的异常处理
10.1 场景需求
- 如果学生年龄 < 0,抛出异常
- 如果学生文件不存在,捕获并提示
- 所有异常需统一记录到日志
class StudentException extends Exception {
public StudentException(String message) {
super(message);
}
}
10.2 应用示例
public void addStudent(String name, int age) throws StudentException {
if (age < 0) {
throw new StudentException("年龄不能为负数");
}
// 其他逻辑
}
10.3 日志捕获所有异常
try {
addStudent("Tom", -5);
} catch (StudentException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
十一、常见错误与误区
错误写法 | 问题说明 | 改进建议 |
catch 块为空 | 捕获了异常却没有任何处理 | 至少记录或提示 |
捕获 Exception 却处理不当 | 容易隐藏真实问题 | 尽量捕获具体异常类型 |
try-catch 滥用(如普通业务流程) | 可读性差,逻辑混乱 | 逻辑分离,用 return/if 替代 |
finally 中使用 return | 可能覆盖前面的异常 | 避免在 finally 中使用 return |
十二、小练习题
- 编写一个函数,传入一个文件路径,读取文件内容并打印,若文件不存在抛出异常。
- 自定义一个异常
PasswordTooShortException
,判断密码长度不足时抛出。 - 模拟学生注册系统,姓名为空或年龄非法抛出异常。
- 使用 try-with-resources 读取文件并打印首行。
- 使用异常链封装多个异常信息,打印完整堆栈。
十三、图示复习:异常流程图
[方法执行]
↓
[出现错误]
↓
try 捕获异常?
↓ ↓
是 否
↓ ↓
catch 处理 异常向上传递
↓
finally 执行(总是执行)
↓
程序继续运行或中止
十四、总结
Java 的异常机制并非只是“处理错误”的工具,它是一种设计思维,强调清晰分工、可预测行为和稳定程序结构。
通过本篇内容,你已经了解并掌握了:
✅ 异常层级、分类与使用场景
✅ try-catch-finally 和 try-with-resources 写法
✅ 自定义异常与异常链封装
✅ 日志记录、错误提示的标准做法
✅ 实战案例与最佳实践