在前六天的学习中,我们掌握了 Java 的基础语法、面向对象核心特性、抽象类与接口等知识。今天我们将学习 Java 中的异常处理机制,这是保证程序健壮性的关键技术。在 JavaWeb 开发中,无论是用户输入错误、数据库连接失败还是网络异常,都需要通过异常处理机制来优雅地处理,避免程序崩溃并给用户友好提示。
什么是异常?
异常(Exception)是程序运行过程中发生的意外情况,它会中断程序的正常执行流程。例如:
- 除以零(
ArithmeticException
) - 访问数组越界(
ArrayIndexOutOfBoundsException
) - 读取不存在的文件(
FileNotFoundException
) - 网络连接中断
没有异常处理的程序遇到异常时会直接崩溃,例如:
public class TestWithoutException {public static void main(String[] args) {int a = 10;int b = 0;System.out.println(a / b); // 发生算术异常System.out.println("程序继续执行"); // 这行代码不会执行}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zeroat TestWithoutException.main(TestWithoutException.java:5)
异常的分类
Java 中的异常体系以Throwable
为根类,主要分为两大类:
Error(错误):
- 由 JVM 产生的严重错误,程序无法处理(如内存溢出
OutOfMemoryError
) - 通常不需要捕获,需要从代码层面解决
- 由 JVM 产生的严重错误,程序无法处理(如内存溢出
Exception(异常):
- 程序可以处理的异常,分为:
- 编译时异常(受检异常):编译期间必须处理的异常(如
IOException
、SQLException
) - 运行时异常(非受检异常):运行时才会发生的异常(如
NullPointerException
、IndexOutOfBoundsException
)
- 编译时异常(受检异常):编译期间必须处理的异常(如
- 程序可以处理的异常,分为:
异常体系结构简图:
Throwable
├─ Error(错误)
│ ├─ OutOfMemoryError
│ └─ StackOverflowError
│
└─ Exception(异常)├─ RuntimeException(运行时异常)│ ├─ NullPointerException│ ├─ ArithmeticException│ └─ IndexOutOfBoundsException│├─ IOException(编译时异常)├─ SQLException(编译时异常)└─ ClassNotFoundException(编译时异常)
异常处理的核心语法
Java 提供了try-catch-finally
和throws
关键字来处理异常,确保程序在遇到异常时能够继续执行或优雅退出。
1. try-catch-finally 语句
try {// 可能发生异常的代码块
} catch (异常类型1 变量名1) {// 处理异常类型1的代码
} catch (异常类型2 变量名2) {// 处理异常类型2的代码
} finally {// 无论是否发生异常,都会执行的代码(通常用于资源释放)
}
执行流程:
- 如果
try
块中没有异常,执行try
块后直接执行finally
块 - 如果
try
块中有异常,中断try
块执行,匹配对应的catch
块处理,最后执行finally
块
实例:
public class TryCatchDemo {public static void main(String[] args) {int[] nums = {1, 2, 3};try {// 可能发生异常的操作int result = 10 / 0; // 算术异常System.out.println(nums[3]); // 数组越界异常(不会执行)} catch (ArithmeticException e) {// 处理算术异常System.out.println("捕获到算术异常:" + e.getMessage());e.printStackTrace(); // 打印异常堆栈信息(调试用)} catch (ArrayIndexOutOfBoundsException e) {// 处理数组越界异常System.out.println("捕获到数组越界异常:" + e.getMessage());} finally {// 无论是否异常,都会执行System.out.println("finally块执行:资源释放操作");}// 异常处理后,程序可以继续执行System.out.println("程序继续运行...");}
}
运行结果:
捕获到算术异常:/ by zero
java.lang.ArithmeticException: / by zeroat TryCatchDemo.main(TryCatchDemo.java:8)
finally块执行:资源释放操作
程序继续运行...
2. throws 声明异常
当方法内部无法处理异常时,可以使用throws
关键字声明该方法可能抛出的异常,由调用者处理:
// 声明方法可能抛出的异常
修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2 {// 可能抛出异常的代码
}
实例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;public class ThrowsDemo {// 声明可能抛出编译时异常public static void readFile(String fileName) throws FileNotFoundException {// 读取文件(可能抛出FileNotFoundException)new FileInputStream(fileName);}public static void main(String[] args) {try {// 调用声明异常的方法,必须处理异常readFile("test.txt");} catch (FileNotFoundException e) {System.out.println("处理文件不存在异常:" + e.getMessage());}}
}
注意:
- 运行时异常可以不声明,但编译时异常必须声明或捕获
- 子类重写父类方法时,抛出的异常不能超过父类方法声明的异常范围
3. throw 主动抛出异常
使用throw
关键字可以在代码中主动抛出异常:
public class ThrowDemo {// 验证年龄的方法public static void checkAge(int age) {if (age < 0 || age > 120) {// 主动抛出异常throw new IllegalArgumentException("年龄不合法:" + age);}System.out.println("年龄验证通过:" + age);}public static void main(String[] args) {try {checkAge(150); // 调用方法} catch (IllegalArgumentException e) {System.out.println("捕获到异常:" + e.getMessage());}}
}
运行结果:
捕获到异常:年龄不合法:150
自定义异常
在实际开发中,系统提供的异常可能无法满足业务需求,这时可以自定义异常类:
- 继承
Exception
(编译时异常)或RuntimeException
(运行时异常) - 提供构造方法(通常需要带消息的构造方法)
实例:
// 自定义编译时异常
public class InsufficientFundsException extends Exception {// 无参构造public InsufficientFundsException() {super();}// 带消息的构造public InsufficientFundsException(String message) {super(message);}
}// 自定义运行时异常
public class InvalidAccountException extends RuntimeException {public InvalidAccountException(String message) {super(message);}
}// 使用自定义异常
public class BankService {private double balance; // 账户余额public BankService(double balance) {this.balance = balance;}// 取款方法(可能抛出自定义异常)public void withdraw(double amount) throws InsufficientFundsException {if (amount <= 0) {throw new InvalidAccountException("取款金额不能为负数");}if (amount > balance) {// 抛出编译时异常,必须声明throw new InsufficientFundsException("余额不足,当前余额:" + balance + ",取款:" + amount);}balance -= amount;System.out.println("取款成功,剩余余额:" + balance);}public static void main(String[] args) {BankService bank = new BankService(1000);try {bank.withdraw(1500);} catch (InsufficientFundsException e) {System.out.println("取款失败:" + e.getMessage());} catch (InvalidAccountException e) {System.out.println("操作失败:" + e.getMessage());}}
}
运行结果:
取款失败:余额不足,当前余额:1000.0,取款:1500.0
异常处理的最佳实践
避免捕获所有异常:不要使用
catch (Exception e)
捕获所有异常,应该针对性处理// 不推荐 try {// ... } catch (Exception e) {// 无法区分具体异常类型 }
不要忽略异常:捕获异常后必须处理(至少记录日志),避免空的
catch
块// 不推荐 try {// ... } catch (IOException e) {// 空块会隐藏错误 }
释放资源:使用
finally
块释放资源(如文件流、数据库连接)FileInputStream fis = null; try {fis = new FileInputStream("test.txt");// 读取文件 } catch (FileNotFoundException e) {e.printStackTrace(); } finally {// 确保关闭流if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}} }
Java 7+:try-with-resources:自动释放实现
AutoCloseable
接口的资源// 推荐:自动关闭资源,无需手动调用close() try (FileInputStream fis = new FileInputStream("test.txt")) {// 读取文件 } catch (IOException e) {e.printStackTrace(); }
使用恰当的异常类型:根据业务场景选择或创建合适的异常类型
异常处理在 JavaWeb 中的应用
在 JavaWeb 开发中,异常处理尤为重要,常见场景包括:
Servlet 中的异常处理:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {try {String username = request.getParameter("username");if (username == null || username.isEmpty()) {throw new IllegalArgumentException("用户名不能为空");}// 处理业务逻辑} catch (IllegalArgumentException e) {// 向客户端返回错误信息response.getWriter().write("错误:" + e.getMessage());response.setStatus(400); // 设置HTTP错误状态码} }
全局异常处理器:在 Spring 等框架中,可以定义全局异常处理器统一处理异常,避免代码冗余
总结与实践
知识点回顾
- 异常概念:程序运行时的意外情况,会中断正常执行流程
- 异常分类:
Error
(无法处理)和Exception
(可处理),Exception
分为编译时异常和运行时异常 - 处理方式:
try-catch-finally
:捕获并处理异常,释放资源throws
:声明方法可能抛出的异常,由调用者处理throw
:主动抛出异常
- 自定义异常:继承
Exception
或RuntimeException
,满足业务需求 - 最佳实践:针对性处理异常、不忽略异常、及时释放资源
实践任务
用户注册异常处理:
- 创建自定义异常
UserAlreadyExistsException
(用户已存在)和InvalidUserInfoException
(用户信息无效) - 编写
UserService
类,包含register(String username, String password)
方法:- 如果用户名已存在(可简单判断是否为 "admin"),抛出
UserAlreadyExistsException
- 如果密码长度小于 6 位,抛出
InvalidUserInfoException
- 否则提示注册成功
- 如果用户名已存在(可简单判断是否为 "admin"),抛出
- 编写测试类,使用
try-catch
处理异常并输出相应信息
- 创建自定义异常
文件读取异常处理:
- 编写
FileUtil
类,包含readFileContent(String filePath)
方法,读取文件内容 - 处理可能的异常(文件不存在、IO 异常等)
- 使用
try-with-resources
确保文件流正确关闭
- 编写
思考:在 Web 开发中,为什么不建议直接将异常堆栈信息返回给客户端?应该如何处理?