开篇概览:异常处理的核心体系
异常(Exception)是程序运行过程中发生的非正常事件,可能导致程序中断。Java 通过结构化异常处理机制,使程序能够捕获、处理或传递异常,从而提升健壮性与可维护性。
Java 异常处理体系包含以下核心内容:
- 异常分类:
- 检查异常(Checked Exception):编译器强制要求处理(如
IOException); - 非检查异常(Unchecked Exception):运行时异常,编译器不强制处理(如
NullPointerException);
- 检查异常(Checked Exception):编译器强制要求处理(如
- 异常处理语法:
try-catch-finally:捕获并处理异常;throw:手动抛出异常对象;throws:声明方法可能抛出的异常;
- 自定义异常:根据业务需求定义专属异常类型;
- 最佳实践:何时捕获、何时抛出、如何记录日志等。
掌握这些内容,可编写出容错性强、易于调试、符合规范的 Java 程序。
一、异常分类:Checked vs Unchecked
Java 中所有异常都继承自 java.lang.Throwable,其下分为两大分支:
Throwable
├── Error(错误,通常不可恢复,如 OutOfMemoryError)
└── Exception
├── RuntimeException(非检查异常)
└── 其他 Exception(检查异常)
1.1 检查异常(Checked Exception)
-
特点:
- 继承自
Exception但不继承RuntimeException; - 编译器强制要求处理(要么
try-catch,要么throws); - 通常表示可预见的外部问题(如文件不存在、网络中断)。
- 继承自
-
常见类型:
IOException:输入输出异常;SQLException:数据库操作异常;ClassNotFoundException:类未找到。
1.2 非检查异常(Unchecked Exception)
-
特点:
- 继承自
RuntimeException; - 编译器不要求处理,但运行时可能抛出;
- 通常表示程序逻辑错误(如空指针、数组越界)。
- 继承自
-
常见类型:
NullPointerException:空指针;ArrayIndexOutOfBoundsException:数组越界;IllegalArgumentException:非法参数;ArithmeticException:算术异常(如除零)。
✅ 关键区别:
特性 Checked Exception Unchecked Exception 是否强制处理 ✅ 是 ❌ 否 继承关系 Exception(非RuntimeException)RuntimeException典型场景 外部环境问题 程序逻辑错误 处理建议 必须处理或声明 修复代码逻辑,或选择性捕获
二、异常处理语法详解
2.1 try-catch-finally 结构
基本语法:
try {
// 可能抛出异常的代码
} catch (ExceptionType1 e) {
// 处理 ExceptionType1
} catch (ExceptionType2 e) {
// 处理 ExceptionType2
} finally {
// 无论是否异常,都会执行(常用于资源清理)
}
示例:处理文件读取异常(Checked Exception)
import java.io.*;
public class FileReadDemo {
public static void main(String[] args) {
BufferedReader reader = null;
try {
// 尝试打开文件(可能抛出 FileNotFoundException)
reader = new BufferedReader(new FileReader("不存在的文件.txt"));
String line = reader.readLine();
System.out.println("文件内容: " + line);
} catch (FileNotFoundException e) {
// 捕获文件未找到异常
System.out.println("【错误】文件未找到: " + e.getMessage());
} catch (IOException e) {
// 捕获其他 IO 异常(如读取失败)
System.out.println("【错误】读取文件时发生 IO 异常: " + e.getMessage());
} finally {
// 无论是否异常,都尝试关闭资源
if (reader != null) {
try {
reader.close();
System.out.println("文件读取器已关闭。");
} catch (IOException e) {
System.out.println("关闭文件时出错: " + e.getMessage());
}
}
}
}
}
💡 注意:
catch块按从具体到一般顺序排列(如先FileNotFoundException,再IOException);finally总是执行(除非 JVM 退出或System.exit())。
2.2 Java 7+ 的 try-with-resources(推荐)
try-with-resources 是 Java 7 引入的一个非常实用的语法糖,用于自动管理资源(如文件流、数据库连接等)。它可以确保在语句结束时每个资源都被自动关闭,避免了繁琐的 finally 块和手动关闭操作。
核心示例:读取文件内容
下面的示例演示了如何使用 try-with-resources 来自动关闭 FileInputStream 和 BufferedReader。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
// 指定要读取的文件路径
String filePath = "example.txt";
// 【关键语法】try-with-resources
// 在 try 关键字后面的括号中声明和初始化一个或多个资源
// 这些资源必须实现了 AutoCloseable 接口(例如所有的 IO 流)
try (// 1. 创建一个 FileReader,用于读取字符文件
FileReader fileReader = new FileReader(filePath);
// 2. 创建一个 BufferedReader,包装 FileReader,提供缓冲功能,提高读取效率
// 注意:多个资源之间用分号 ; 隔开
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
// 这里是 try 块的正常代码逻辑,用于处理资源
System.out.println("开始读取文件内容:");
String line;
// 循环读取文件的每一行,直到返回 null(表示文件结束)
while ((line = bufferedReader.readLine()) != null) {
// 打印每一行的内容
System.out.println(line);
}
System.out.println("文件读取完毕。");
} catch (IOException e) {
// 捕获并处理在创建资源或处理资源过程中可能发生的IO异常
// 例如:文件不存在、无读取权限等
System.err.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
}
// 【核心优势】当程序执行离开 try 块(无论是正常结束还是发生异常)时,
// JVM 会自动调用 bufferedReader 和 fileReader 的 .close() 方法,
// 并且关闭的顺序与声明的顺序相反(先声明的后关闭)。
// 我们不需要在 finally 块中手动关闭它们,代码更简洁,且绝不会忘记关闭资源。
}
}
与传统 try-catch-finally 方式的对比
为了突出 try-with-resources 的优势,我们看一下传统写法:
// 传统方式:需要在 finally 中手动关闭资源
BufferedReader bufferedReader = null;
FileReader fileReader = null;
try {
fileReader = new FileFileReader(filePath);
bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
} finally {
// 手动关闭资源,代码冗长且容易出错
if (bufferedReader != null) {
try {
bufferedReader.close(); // 关闭外层流
} catch (IOException e) {
e.printStackTrace(); // 关闭时也可能发生异常,需要处理
}
}
if (fileReader != null) {
try {
fileReader.close(); // 关闭内层流
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结 try-with-resources 的优点:
✅ 优势:
- 简洁性:代码量大幅减少,更易读。
- 可靠性:由 Java 自动管理资源关闭,杜绝了因忘记在
finally块中关闭而导致的资源泄漏。- 异常处理更清晰:如果在
try块内发生异常,并且关闭资源时也发生了异常,try-with-resources会抑制关闭时抛出的异常,并将第一个异常(即try块内的异常)作为主要异常抛出,关闭时的异常会被添加到主要异常的“被抑制的异常”列表中,可以通过getSuppressed()方法获取。这避免了异常信息被覆盖,使得问题根因更容易被定位。
因此,在处理任何实现了 AutoCloseable 接口的资源时,都应优先使用 try-with-resources。
2.3 throw 与 throws
| 关键字 | 作用 | 使用位置 |
|---|---|---|
throw | 抛出一个异常对象 | 方法体内 |
throws | 声明方法可能抛出的异常类型 | 方法签名 |
示例:throw 手动抛出异常
public class ThrowDemo {
public static void checkAge(int age) {
if (age < 0) {
// 手动抛出非法参数异常(Unchecked)
throw new IllegalArgumentException("年龄不能为负数: " + age);
}
if (age > 150) {
// 抛出自定义异常(见后文)
throw new InvalidAgeException("年龄超出合理范围: " + age);
}
System.out.println("年龄验证通过: " + age + " 岁");
}
public static void main(String[] args) {
try {
checkAge(-5); // 触发异常
} catch (IllegalArgumentException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
}
示例:throws 声明异常
import java.io.*;
public class ThrowsDemo {
// 声明该方法可能抛出 IOException(Checked Exception)
public static void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename); // 可能抛出 FileNotFoundException
// ... 读取文件逻辑
file.close();
}
public static void main(String[] args) {
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("调用方处理异常: " + e.getMessage());
}
}
}
✅ 规则:
throws用于传递异常给调用者;- Checked Exception 必须通过
throws或try-catch处理;- Unchecked Exception 可选择是否
throws。
三、自定义异常
当标准异常无法准确描述业务错误时,应定义自定义异常。
3.1 定义自定义异常类
- 继承
Exception(Checked)或RuntimeException(Unchecked); - 通常提供构造方法(含消息、原因)。
示例:定义业务异常
// 中文注释:自定义“无效年龄异常”,继承 RuntimeException(非检查异常)
class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message); // 调用父类构造器
}
public InvalidAgeException(String message, Throwable cause) {
super(message, cause);
}
}
// 中文注释:自定义“账户余额不足异常”,继承 Exception(检查异常)
class InsufficientBalanceException extends Exception {
private double currentBalance;
private double requiredAmount;
public InsufficientBalanceException(double current, double required) {
super("余额不足!当前余额: " + current + ", 需要: " + required);
this.currentBalance = current;
this.requiredAmount = required;
}
// 提供额外信息的 getter
public double getCurrentBalance() { return currentBalance; }
public double getRequiredAmount() { return requiredAmount; }
}
3.2 使用自定义异常
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// 取款方法:可能抛出检查异常
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
// 抛出自定义检查异常
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
System.out.println("成功取款 " + amount + " 元,当前余额: " + balance);
}
public static void main(String[] args) {
BankAccount account = new BankAccount(100.0);
try {
account.withdraw(150.0); // 余额不足,抛出异常
} catch (InsufficientBalanceException e) {
System.out.println("【业务异常】" + e.getMessage());
System.out.println("差额: " + (e.getRequiredAmount() - e.getCurrentBalance()) + " 元");
}
}
}
✅ 自定义异常设计建议:
- 业务规则错误 → Unchecked(如参数非法);
- 外部依赖失败 → Checked(如支付失败、网络超时);
- 包含足够上下文信息(如错误码、参数值)。
四、异常处理最佳实践
✅ 推荐做法
- 不要忽略异常:
try { ... } catch (Exception e) { // ❌ 错误:空 catch 块 } - 记录日志:
catch (IOException e) { logger.error("文件读取失败", e); // 记录堆栈 throw new ServiceException("服务不可用", e); // 转换为业务异常 } - 使用具体异常类型:
- 避免
catch (Exception e),优先捕获具体异常;
- 避免
- 资源使用
try-with-resources; - 自定义异常命名清晰(如
UserNotFoundException)。
❌ 避免做法
- 在
catch中仅打印e.printStackTrace()(生产环境应记录日志); - 捕获异常后不处理也不抛出(“吞异常”);
- 在
finally中使用return(会覆盖try中的return)。
五、总结
| 机制 | 说明 | 使用场景 |
|---|---|---|
| Checked Exception | 编译器强制处理 | 外部环境问题(IO、DB) |
| Unchecked Exception | 运行时异常 | 程序逻辑错误 |
| try-catch-finally | 捕获并处理异常 | 局部错误恢复 |
| try-with-resources | 自动资源管理 | 文件、网络、数据库连接 |
| throw | 主动抛出异常 | 业务校验失败 |
| throws | 声明异常传递 | 方法不处理,交由调用者 |
| 自定义异常 | 业务语义化错误 | 提升代码可读性与维护性 |
📌 核心思想:
“异常是程序的一部分,不是错误的代名词。”
合理使用异常机制,能让程序在面对不确定性时优雅降级、快速定位、安全恢复,是构建高可靠系统的关键能力。
Java异常处理机制详解

被折叠的 条评论
为什么被折叠?



