Java异常处理机制:从入门到实战,覆盖 try-catch-finally、throws、自定义异常及性能分析
手写重试框架、try-with-resources 性能对比、自定义登录异常
📖 前言
异常处理是 Java 程序健壮性的基石。很多人会用 try-catch,但说不清 finally 什么时候执行、throws 和 throw 的区别、自定义异常的设计原则、以及异常对性能的影响。本文通过五个实验带你全面掌握异常处理,并实现一个生产级重试工具类。
🧠 一、知识点速览(背下这张表)
| 关键词 | 作用 | 使用位置 |
|---|---|---|
try |
包裹可能抛出异常的代码 | 方法内 |
catch |
捕获并处理特定异常 | 紧随 try 或 finally 之前 |
finally |
无论是否发生异常都会执行的代码块 | 可选,放在最后 |
throws |
声明方法可能抛出的异常(向上传递) | 方法签名 |
throw |
手动抛出异常对象 | 方法内 |
try-with-resources |
自动关闭实现了 AutoCloseable 的资源 |
Java 7+ |
异常体系简图:
Throwable
├── Error(系统级错误,不可处理)
└── Exception
├── RuntimeException(非受检异常,如 NullPointerException)
└── 其他 Exception(受检异常,如 IOException)
💡 最佳实践:能用
if判断避免的异常,尽量不要用异常处理(性能损耗大);自定义异常要继承Exception或RuntimeException,并提供有意义的 message。
📋 二、题目驱动
题目:阐述 Java 异常处理机制的核心组成,并设计一个用户登录模块,要求使用自定义异常处理用户名或密码错误的情况。同时分析 try-catch 对性能的影响。
🛠️ 三、手把手实操(附完整代码 + 截图位置)
3.1 基础异常处理演示
运行 ExceptionBasics.java 的控制台输出及完整代码:
完整代码:
public class ExceptionBasics {
public static void main(String[] args) {
// 1. ArithmeticException
try {
int a = 10 / 0;
System.out.println("这行不会执行");
} catch (ArithmeticException e) {
System.out.println("捕获算术异常: " + e.getMessage());
}
// 2. ArrayIndexOutOfBoundsException
try {
int[] arr = new int[3];
arr[5] = 100;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获数组越界异常: " + e.getMessage());
}
// 3. 多重 catch 和 finally
try {
String str = null;
System.out.println(str.length());
} catch (NullPointerException e) {
System.out.println("空指针异常: " + e);
} catch (Exception e) {
System.out.println("其他异常: " + e);
} finally {
System.out.println("finally 块总会执行");
}
// 4. throws 声明异常
try {
methodThrowsException();
} catch (InterruptedException e) {
System.out.println("捕获线程中断异常: " + e);
}
}
public static void methodThrowsException() throws InterruptedException {
Thread.sleep(1000);
System.out.println("sleep 结束");
}
}
预期输出:
捕获算术异常: / by zero
捕获数组越界异常: Index 5 out of bounds for length 3
空指针异常: java.lang.NullPointerException
finally 块总会执行
sleep 结束
捕获线程中断异常: java.lang.InterruptedException
🔍 要点:
-
一个
try可跟多个catch,注意异常类型从上到下具体到一般。 -
finally在return之前执行,除非System.exit()。 -
throws将异常抛给调用者处理。
3.2 自定义异常 + 登录验证
运行 LoginExceptionDemo.java 的控制台输出
完整代码:
预期输出:
尝试登录: 用户名=, 密码=123456 -> 失败: 用户名不能为空
尝试登录: 用户名=admin, 密码= -> 失败: 密码不能为空
尝试登录: 用户名=wrong, 密码=123456 -> 失败: 用户名错误
尝试登录: 用户名=admin, 密码=wrong -> 失败: 密码错误
尝试登录: 用户名=admin, 密码=123456 -> 登录成功!
设计要点:
-
自定义异常类通常提供带
String参数的构造器。 -
业务方法抛出异常,由上层统一捕获处理。
3.3 创新点:try-with-resources 与性能对比
运行 TryWithResourcesDemo.java 的控制台输出(尤其关注性能对比数字):
完整代码:

预期输出示例:
传统方式读取: Hello, Exception!
try-with-resources 读取: Hello, Exception!
性能对比: 无异常耗时 = 2.34 ms, 有异常耗时 = 345.67 ms
结论:
-
异常对象的创建和填充堆栈轨迹非常耗时(比普通判断慢两个数量级)。
-
能用逻辑判断避免的异常,绝不用异常控制流程。
3.4 竞赛题:实现重试工具类(模拟网络请求)
运行 RetryUtils.java 的控制台输出及完整代码:
完整代码:
java
import java.util.function.Supplier;
public class RetryUtils {
public static <T> T retry(Supplier<T> task, int retryTimes, long delayMs,
Class<? extends Exception> exceptionClass) throws Exception {
Exception lastException = null;
for (int i = 0; i < retryTimes; i++) {
try {
return task.get();
} catch (Exception e) {
lastException = e;
if (exceptionClass.isInstance(e) && i < retryTimes - 1) {
System.out.printf("第%d次执行失败: %s,%dms后重试...\n", i+1, e.getMessage(), delayMs);
Thread.sleep(delayMs);
} else {
throw e;
}
}
}
throw lastException;
}
private static int unreliableService(int id) throws Exception {
if (Math.random() < 0.7) {
throw new Exception("服务调用失败,id=" + id);
}
return id * 100;
}
public static void main(String[] args) throws Exception {
Integer result = retry(() -> unreliableService(42), 5, 500, Exception.class);
System.out.println("最终成功,结果: " + result);
}
}
预期输出示例:
text
第1次执行失败: 服务调用失败,id=42,500ms后重试...
第2次执行失败: 服务调用失败,id=42,500ms后重试...
第3次执行成功,结果: 4200
最终成功,结果: 4200
竞赛价值:
-
考察函数式接口(
Supplier<T>)、泛型、异常处理、重试策略。 -
可扩展为带退避算法(指数退避)的高级重试框架。
-
是真实项目中(如 RPC 调用、数据库连接)常用技巧。
📝 四、总结与最佳实践
| 规范 | 说明 |
|---|---|
| ❌ 不要忽略异常 | catch 块至少打印日志,或者重新抛出 |
| ✅ 使用特定的异常类型 | 避免 catch (Exception e) 万能处理 |
| ✅ 早抛出,晚捕获 | 底层抛出具体异常,高层统一处理 |
✅ 资源使用 try-with-resources |
比 finally 手动关闭更简洁安全 |
| ⚡ 性能敏感代码避免异常 | 用 if 预防代替异常捕获 |
📦 自定义异常要提供带 cause 的构造器 |
便于链式追踪 |
🎯 阿里巴巴 Java 开发手册:异常不要用来做流程控制;finally 块中不要使用 return。
📚 参考文献
-
Java 官方异常处理教程
-
《Java面向对象程序设计(第四版)》
-
《Effective Java》第三版 - 异常章节
🙏 写在最后
如果你觉得这篇文章对你有帮助,请点赞👍 + 收藏⭐ + 评论💬 支持一下!
你的鼓励是我持续输出硬核技术文章的动力。
更多推荐

所有评论(0)