一、为什么需要异常处理?
想象你在开车时突然遇到道路施工,导航系统会立刻提醒你绕行——异常处理就是程序的导航系统。当程序运行时遇到意外情况(如文件找不到、网络断开),异常处理机制能让程序优雅地处理问题,而不是直接崩溃。
二、Java异常的家族图谱
Java异常分为两大类:必须处理的检查型异常(Checked Exceptions)和可选择性处理的非检查型异常(Unchecked Exceptions)。
1. 检查型异常(Checked Exceptions)
- 特点:编译时强制处理,否则代码无法通过编译。
- 常见类型:
IOException
、SQLException
。 - 场景:外部依赖可能出错的情况(如读取文件、数据库操作)。
2. 非检查型异常(Unchecked Exceptions)
- 特点:通常由代码逻辑错误导致,不强制处理。
- 常见类型:
NullPointerException
、ArrayIndexOutOfBoundsException
。 - 场景:程序员可预防的错误(如空指针、数组越界)。
三、异常处理五大关键字
1. try-catch:异常捕获
try {
FileReader file = new FileReader("test.txt"); // 可能抛出FileNotFoundException
} catch (FileNotFoundException e) {
System.out.println("文件没找到,换个路径试试?");
e.printStackTrace(); // 打印异常堆栈(实际项目要替换为日志)
}
2. finally:最终清理
无论是否发生异常,finally
中的代码必须执行,常用于释放资源(如关闭文件、数据库连接)。
FileReader file = null;
try {
file = new FileReader("test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close(); // 确保文件关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用finally块的细节说明:
(1)finally块不执行的极端场景
当JVM调用System.exit()或线程被中断时,finally块不会执行:
try {
System.exit(1);
} finally {
System.out.println("永远不会执行"); // 不会输出
}
(2)异常覆盖效应
finally块中抛出异常会覆盖try/catch中的原始异常:
public static void main(String[] args) {
try {
throw new IOException("原始IO异常");
} finally {
throw new RuntimeException("覆盖异常"); // 最终抛出RuntimeException
}
}
(3)返回值覆盖陷阱
finally中的return语句会覆盖try/catch块的返回值:
public static int getValue() {
try {
return 1;
} finally {
return 2; // 实际返回2
}
}
3. throw:手动抛异常
主动抛出异常,常用于参数校验。
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数!");
}
this.age = age;
}
4. throws:声明异常
在方法签名中声明可能抛出的异常,调用者需处理。
public void readFile() throws FileNotFoundException {
FileReader file = new FileReader("test.txt");
}
四、自定义异常:打造专属错误类型
当Java内置异常不满足需求时,可创建自定义异常类。
1. 定义异常类
// 继承Exception表示检查型异常(必须处理)
public class InvalidCredentialsException extends Exception {
public InvalidCredentialsException(String message) {
super(message); // 调用父类构造方法
}
}
2. 使用自定义异常
public void login(String username, String password) throws InvalidCredentialsException {
if (!isValidUser(username, password)) {
throw new InvalidCredentialsException("用户名或密码错误!");
}
// 登录成功逻辑
}
五、异常处理最佳实践
1. 不要吞掉异常
// 错误示范:异常被静默处理,问题被隐藏
try {
riskyOperation();
} catch (Exception e) {
// 空的catch块!
}
2. 精准捕获异常
避免捕获过于宽泛的Exception
。
try {
// 可能抛出多种异常
} catch (FileNotFoundException e) {
// 处理文件不存在
} catch (IOException e) {
// 处理其他IO问题
}
3. 使用try-with-resources自动关闭资源
Java 7+ 提供更简洁的资源管理方式。
// 自动关闭文件(无需finally)
try (FileReader file = new FileReader("test.txt")) {
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
六、综合案例:用户登录系统
public class LoginService {
public void login(String username, String password)
throws InvalidCredentialsException, DatabaseConnectionException {
try {
// 1. 验证用户名密码
if (username == null || password == null) {
throw new IllegalArgumentException("用户名或密码为空");
}
// 2. 连接数据库(可能抛出SQLException)
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb");
// 3. 查询用户信息
// ...(省略查询逻辑)
} catch (SQLException e) {
throw new DatabaseConnectionException("数据库连接失败", e); // 包装原始异常
}
}
}
七、总结
- 核心原则:早抛出(Fail-Fast)、晚捕获(Catch Responsibly)。
- 工具选择:
- 简单错误 → 使用内置异常
- 业务特定错误 → 自定义异常
- 避免误区:不要用异常控制流程(如用异常代替
if
判断)。