Java异常处理:中小厂面试通关指南
2小时速通 | 面试必备 | 实战导向
一、异常体系全景图
1.1 继承关系
Throwable (所有异常的根类)
├── Error (系统级错误,不可恢复)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (程序异常,可处理)
├── RuntimeException (运行时异常,非受检)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── NumberFormatException
│ └── ArithmeticException
└── 其他Exception (编译时异常,受检)
├── IOException
├── SQLException
├── ClassNotFoundException
└── InterruptedException
1.2 两大分类对比
| 特性 | 编译时异常 (Checked) | 运行时异常 (Unchecked) |
|---|---|---|
| 继承关系 | Exception (非RuntimeException) | RuntimeException |
| 编译要求 | 必须显式处理 (try-catch/throws) | 无强制要求 |
| 发生时机 | 外部因素 (IO、网络、数据库) | 代码逻辑问题 |
| 处理建议 | 必须处理 | 可提前规避 |
| 面试重点 | 了解常用类型 | 高频考点 ⭐⭐⭐ |
二、异常核心概念
2.1 四大关键字
try-catch:捕获并处理异常
java
try {
// 可能抛出异常的代码
} catch (SpecificException e) {
// 处理特定异常
} catch (Exception e) {
// 处理其他异常(放最后)
}
throws:声明方法可能抛出的异常
java
public void readFile(String path) throws IOException {
// 方法实现
}
throw:手动抛出异常对象
java
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
finally:无论如何都会执行的代码块
java
try {
// 业务代码
} finally {
// 资源清理(关闭文件、释放连接等)
// ⚠️ 严禁在此写 return
}
2.2 异常处理流程
程序执行
↓
遇到异常
↓
JVM创建异常对象
↓
是否有try-catch?
├─ 是 → 匹配catch块 → 执行finally → 继续执行
└─ 否 → 向上抛出 → 最终到main → 程序终止
三、常见异常速查表
3.1 运行时异常 (重点掌握)
| 异常类 | 触发场景 | 代码示例 | 解决方案 |
|---|---|---|---|
| NullPointerException | 调用null对象的方法/属性 | String s = null; s.length(); |
判空:if (s != null) |
| IllegalArgumentException | 方法参数不合法 | setAge(-1) |
参数校验 + 抛异常 |
| ArrayIndexOutOfBoundsException | 数组下标越界 | arr[10] (数组长度5) |
校验:if (i < arr.length) |
| NumberFormatException | 字符串转数字失败 | Integer.parseInt("abc") |
try-catch 或正则预校验 |
| ClassCastException | 类型强转失败 | (Dog) animal |
先用 instanceof 判断 |
| ArithmeticException | 数学运算错误 | 10 / 0 |
校验除数不为0 |
| IndexOutOfBoundsException | 集合索引越界 | list.get(100) |
校验索引范围 |
| ConcurrentModificationException | 并发修改集合 | 遍历时删除元素 | 使用迭代器删除 |
3.2 编译时异常 (了解即可)
| 异常类 | 触发场景 | 处理方式 |
|---|---|---|
| IOException | 文件读写、网络IO失败 | try-catch 或 throws |
| SQLException | 数据库操作失败 | 统一捕获处理 |
| ClassNotFoundException | 反射找不到类 | try-catch |
| InterruptedException | 线程中断 | 恢复中断状态 |
四、异常处理最佳实践
4.1 业务场景对应规范
| 场景 | 推荐做法 | 示例 |
|---|---|---|
| 参数校验失败 | 抛自定义运行时异常 | throw new ParamException("用户名不能为空") |
| 业务逻辑违规 | 抛自定义业务异常 | throw new BusinessException("余额不足") |
| 文件/IO操作 | 捕获IOException | try { ... } catch (IOException e) |
| 数据库操作 | 捕获SQLException | 统一异常处理器 |
| 代码逻辑疏漏 | 提前规避 | 判空、范围校验 |
4.2 自定义异常模板
java
/**
* 业务异常基类
*/
public class BusinessException extends RuntimeException {
private int code;
private String message;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
4.3 异常处理原则
✅ 应该做的
- 捕获具体异常,而非Exception
- 记录异常日志(包含堆栈信息)
- 给用户友好的错误提示
- 在finally中释放资源
- 自定义异常继承RuntimeException
❌ 不应该做的
- 空catch块(吞掉异常)
- finally中写return
- 过度使用异常(正常逻辑用if判断)
- 捕获Error(系统级错误)
- 异常信息暴露敏感数据
五、面试手撕真题
真题1:字符串转整数(基础必考)
题目要求:
- 实现
stringToInt(String s)方法 - 处理 null、空字符串、非数字字符串
- 异常时返回0并打印错误信息
- 禁止空catch块
标准答案:
java
public class StringConverter {
public int stringToInt(String s) {
// 1. 处理null
if (s == null) {
System.out.println("错误:字符串为null");
return 0;
}
// 2. 处理空字符串
if (s.trim().isEmpty()) {
System.out.println("错误:字符串为空");
return 0;
}
// 3. 尝试转换
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
System.out.println("错误:'" + s + "' 无法转为数字");
return 0;
}
}
// 测试
public static void main(String[] args) {
StringConverter converter = new StringConverter();
System.out.println(converter.stringToInt("123")); // 123
System.out.println(converter.stringToInt("abc")); // 0
System.out.println(converter.stringToInt(null)); // 0
System.out.println(converter.stringToInt("")); // 0
}
}
考点分析:
- 边界条件处理(null、空字符串)
- 具体异常捕获(NumberFormatException)
- 异常信息输出
- 返回默认值
真题2:自定义参数校验异常(高频)
题目要求:
- 自定义
ParamValidException,继承RuntimeException - 包含 code(异常码)和 msg(异常信息)
- 实现用户注册校验:用户名≥6位,密码≥8位
标准答案:
java
/**
* 自定义参数校验异常
*/
class ParamValidException extends RuntimeException {
private int code;
private String msg;
public ParamValidException(String msg) {
super(msg);
this.code = 400;
this.msg = msg;
}
public ParamValidException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
/**
* 用户注册服务
*/
public class UserService {
public void register(String username, String password) {
// 用户名校验
if (username == null || username.length() < 6) {
throw new ParamValidException(400, "用户名长度不能小于6位");
}
// 密码校验
if (password == null || password.length() < 8) {
throw new ParamValidException(400, "密码长度不能小于8位");
}
System.out.println("注册成功:" + username);
}
// 测试
public static void main(String[] args) {
UserService service = new UserService();
try {
service.register("user", "12345678"); // 用户名太短
} catch (ParamValidException e) {
System.out.println("异常码:" + e.getCode());
System.out.println("异常信息:" + e.getMsg());
}
try {
service.register("username", "123"); // 密码太短
} catch (ParamValidException e) {
System.out.println("异常码:" + e.getCode());
System.out.println("异常信息:" + e.getMsg());
}
service.register("username", "12345678"); // 成功
}
}
考点分析:
- 自定义异常类设计
- 继承RuntimeException(无需throws)
- 构造方法重载
- 业务参数校验
- 异常信息封装
真题3:try-catch-finally执行顺序(坑点题)
题目:分析以下代码的执行顺序和返回值
java
public class FinallyTest {
public static int test() {
int num = 10;
try {
System.out.println("1. try块执行");
num = num / 0; // 抛出异常
return num;
} catch (ArithmeticException e) {
System.out.println("2. catch块执行");
num = 20;
return num;
} finally {
System.out.println("3. finally块执行");
num = 30;
return num; // ⚠️ 问题代码
}
}
public static void main(String[] args) {
System.out.println("最终返回值:" + test());
}
}
输出结果:
1. try块执行
2. catch块执行
3. finally块执行
最终返回值:30
问题分析:
- try块抛出异常,跳转到catch
- catch块准备返回20,但finally必须先执行
- finally块执行并返回30,覆盖了catch的返回值
- 严重问题:finally中的return会覆盖正常返回值
正确写法:
java
public static int test() {
int num = 10;
try {
System.out.println("1. try块执行");
num = num / 0;
return num;
} catch (ArithmeticException e) {
System.out.println("2. catch块执行");
num = 20;
return num;
} finally {
System.out.println("3. finally块执行");
num = 30; // 只修改变量,不return
// 资源清理代码
}
}
// 此时返回值为20(正确)
考点分析:
- try-catch-finally执行顺序
- finally中return的坑
- 异常处理流程
- 代码规范
六、面试高频问答
Q1:Exception和Error的区别?
答:
- Exception:程序异常,可预见可处理,如空指针、IO异常
- Error:系统级错误,不可恢复,如内存溢出、栈溢出
- 关键区别:Exception应该捕获处理,Error无需处理(程序无法恢复)
Q2:运行时异常和编译时异常的区别?
答:
- 编译时异常:继承Exception但非RuntimeException,编译期必须处理
- 运行时异常:继承RuntimeException,编译期无强制要求
- 使用场景:编译时异常用于外部因素(IO、网络),运行时异常用于代码逻辑问题
Q3:throw和throws的区别?
答:
- throw :方法内部手动抛出异常对象,
throw new Exception() - throws :方法签名声明可能抛出的异常,
void method() throws Exception - 记忆技巧:throw抛对象,throws声明类型
Q4:finally一定会执行吗?
答:
不一定,以下情况不执行:
- JVM退出(System.exit())
- 守护线程中,主线程结束
- 死循环或死锁
- 断电、进程被杀
正常情况下,即使try/catch中有return,finally也会执行
Q5:如何自定义异常?
答:
java
// 1. 继承RuntimeException(推荐)或Exception
public class CustomException extends RuntimeException {
private int code;
// 2. 提供构造方法
public CustomException(String message) {
super(message);
}
public CustomException(int code, String message) {
super(message);
this.code = code;
}
// 3. 提供getter方法
public int getCode() {
return code;
}
}
Q6:try-catch会影响性能吗?
答:
- 正常流程:性能影响极小,可忽略
- 异常抛出:创建异常对象、填充堆栈,有一定开销
- 建议:不要用异常控制正常业务流程,用if判断
Q7:多个catch块的顺序有要求吗?
答:
有要求,必须从子类到父类排列:
java
try {
// 代码
} catch (FileNotFoundException e) { // 子类
// 处理
} catch (IOException e) { // 父类
// 处理
} catch (Exception e) { // 最父类
// 处理
}
原因:Java按顺序匹配catch块,父类会捕获所有子类异常
七、避坑指南
坑点1:空catch块
java
// ❌ 错误:吞掉异常
try {
// 代码
} catch (Exception e) {
// 什么都不做
}
// ✅ 正确:至少打印日志
try {
// 代码
} catch (Exception e) {
e.printStackTrace();
// 或使用日志框架
log.error("操作失败", e);
}
坑点2:finally中return
java
// ❌ 错误:覆盖正常返回值
try {
return 1;
} finally {
return 2; // 最终返回2,隐藏了try的返回
}
// ✅ 正确:finally只做清理
try {
return 1;
} finally {
// 关闭资源
connection.close();
}
坑点3:捕获Exception
java
// ❌ 错误:无法区分异常类型
try {
// 代码
} catch (Exception e) {
// 所有异常都这样处理
}
// ✅ 正确:捕获具体异常
try {
// 代码
} catch (IOException e) {
// IO异常处理
} catch (SQLException e) {
// 数据库异常处理
}
坑点4:资源未关闭
java
// ❌ 错误:异常时资源泄漏
FileInputStream fis = new FileInputStream("file.txt");
try {
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
// 如果try中抛异常,fis未关闭
// ✅ 正确:使用try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
// 自动关闭资源
坑点5:异常信息暴露敏感数据
java
// ❌ 错误:暴露数据库信息
throw new Exception("数据库连接失败:jdbc:mysql://192.168.1.100:3306/db");
// ✅ 正确:友好提示
throw new Exception("系统繁忙,请稍后重试");
八、核心知识点速记卡
🎯 必背知识点
-
异常分类
- 编译时异常:必须处理,外部因素
- 运行时异常:可选处理,代码逻辑
-
四大关键字
- try-catch:捕获处理
- throws:声明抛出
- throw:手动抛出
- finally:必定执行(资源清理)
-
常见异常
- NullPointerException:空指针
- IllegalArgumentException:参数非法
- NumberFormatException:数字格式
- IOException:IO操作
- SQLException:数据库操作
-
自定义异常
- 继承RuntimeException
- 包含code和message
- 提供构造方法和getter
-
处理原则
- 捕获具体异常
- 记录日志
- 友好提示
- finally清理资源
- 禁止空catch
- 禁止finally中return
📝 面试检查清单
- 能说清异常体系结构
- 能区分编译时和运行时异常
- 能手写字符串转整数(含异常处理)
- 能手写自定义异常类
- 能说清try-catch-finally执行顺序
- 能说清finally中return的坑
- 能列举5个以上常见异常
- 能说清throw和throws的区别
- 能说清Exception和Error的区别
- 知道try-with-resources用法
九、实战代码模板
模板1:统一异常处理器(Spring Boot)
这个在博主之前的博文里面有,感兴趣的可以翻看博主以前的文章哦!😊
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ParamValidException.class)
public Result handleParamValid(ParamValidException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后重试");
}
}
模板2:业务方法异常处理
java
public void businessMethod(String param) {
// 1. 参数校验
if (param == null || param.isEmpty()) {
throw new ParamValidException("参数不能为空");
}
// 2. 业务逻辑
try {
// 数据库操作
dao.save(param);
} catch (SQLException e) {
log.error("数据库操作失败", e);
throw new BusinessException("操作失败,请重试");
}
}
模板3:资源管理
java
// 推荐:try-with-resources
public String readFile(String path) {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
} catch (IOException e) {
log.error("文件读取失败", e);
return null;
}
}
十、学习建议
2小时学习路径
第1小时:理论基础
- 0-20分钟:异常体系和分类
- 20-40分钟:四大关键字用法
- 40-60分钟:常见异常速查
第2小时:实战练习
- 0-30分钟:手撕3道真题
- 30-50分钟:背诵面试问答
- 50-60分钟:复习避坑指南
进阶学习
- 源码阅读:Throwable类的实现
- 框架应用:Spring的异常处理机制
- 性能优化:异常对性能的影响
- 日志规范:异常日志的最佳实践
结语
Java异常处理是面试必考基础,中小厂重点考察:
- 基础概念理解
- 常见异常识别
- 手写代码能力
- 规范和避坑
掌握本文内容,足以应对90%的异常相关面试题。建议:
- 理论部分理解记忆
- 真题代码手写3遍
- 面试前快速复习速记卡
祝面试顺利!💪
作者:[识君啊]