Java异常
Java异常
一、前言
在日常 Java 开发中,空指针、数组越界、类型转换异常、IO异常 无处不在。
所以我们需要知道:
• 异常底层是怎么抛出来的?
• throws 和 throw 到底差在哪?
• 什么时候捕获异常?什么时候直接抛出?
• 为什么不要 catch Exception?
• 业务中为什么要自定义异常?
二、什么是异常?
2.1 定义
异常(Exception):程序运行过程中,违背正常逻辑、可预料、可捕获的非正常情况。
区别:
• 错误:程序无法恢复的致命问题(内存溢出、栈溢出)
• 异常:程序可捕获、可处理、可恢复的问题
Java 把非正常情况封装成异常对象,面向对象统一处理。
三、Java 异常整体体系(核心结构图)
Java 异常顶层父类:Throwable
体系树:
Throwable
├─ Error(系统级错误,无需处理)
└─ Exception(程序级异常,可处理)
├─ 编译异常(受检异常 CheckedException)
└─ 运行异常(运行时异常 RuntimeException)
3.1 Error 错误
代表系统层面的错误:
• StackOverflowError 栈溢出
• OutOfMemoryError 内存溢出
特点:
-
程序无法处理
-
不需要捕获
-
代码层面无能为力,只能优化代码
3.2 Exception 异常(重点)
所有我们需要处理的异常都在这里,分为两大类:
1)受检异常(编译异常)
编译期间必须处理,否则代码报错无法运行
常见:
• IOException IO读写异常
• FileNotFoundException 文件不存在
• SQLException 数据库异常
特点:
• 继承自 Exception 但不继承 RuntimeException
• 强制处理:要么 try-catch,要么 throws 抛出
2)非受检异常(运行时异常)
编译不报错,运行才崩溃
常见:
• NullPointerException 空指针
• ArrayIndexOutOfBoundsException 数组越界
• ClassCastException 类型转换异常
• IllegalArgumentException 参数非法
特点:
• 编译器不强制捕获
• 属于代码不严谨导致,可以通过逻辑规避
四、五大异常关键字彻底讲透
4.1 try、catch、finally
标准结构:
try{
// 可能出错的代码
}catch(异常类型 e){
// 异常处理逻辑
}finally{
// 一定会执行(除非JVM退出)
}
执行规则
-
try 无异常:跳过 catch,执行 finally
-
try 有异常:立刻跳转到对应 catch,后续 try 代码不执行
-
finally 无论是否异常都会执行
finally 唯一不执行的情况****
System.exit(0);
JVM 直接退出,finally 不会执行
4.2 throw:手动抛出异常
作用:主动制造异常对象抛出
if(age < 0){
throw new IllegalArgumentException("年龄不能为负数");
}
4.3 throws:声明异常
作用:告知调用者:我这个方法可能报错,需要自己处理
public void readFile() throws IOException{
// IO操作
}
**throw vs throws **
-
throw:主动抛异常,运行时动作(代码内部)
-
throws:异常声明,编译时提醒(方法定义上)
-
throw 是动作,throws 是提醒
五、异常执行流程
正常流程
-
代码出现异常
-
JVM 自动创建对应异常对象
-
抛出异常
-
寻找就近的 try-catch
-
捕获后执行处理逻辑
-
执行 finally
-
程序继续运行
无捕获流程
异常逐层向上抛 → 最终抛给 JVM → 程序终止、控制台打印堆栈信息
六、常见异常用法与规范
6.1 多 catch 捕获顺序
先小后大
正确:
catch(NullPointerException e)
catch(Exception e)
错误:
父类在前,子类永远无法匹配
6.2 try-catch-finally 中 return 的执行顺序
-
return 在 finally 之前执行,但返回值暂存
-
finally 执行完,再真正返回
6.3 为什么不建议直接 catch(Exception)?
-
太宽泛,无法精准定位错误
-
容易吞掉未知异常,导致 bug 隐藏
-
不利于日志排查问题
七、自定义异常(项目必备)
系统自带异常只能描述技术问题,业务问题必须自定义异常
7.1 为什么需要自定义异常?
• 区分「系统异常」和「业务异常」
• 统一项目错误码、错误信息
• 方便全局异常拦截、统一返回前端
7.2 自定义业务异常示例
// 自定义运行时业务异常
public class BusinessException extends RuntimeException{
private Integer code;
private String msg;
public BusinessException(Integer code,String msg){
super(msg);
this.code = code;
this.msg = msg;
}
}
业务使用:
public void login(String username){
if(username == null){
throw new BusinessException(400,"用户名不能为空");
}
}
八、异常最佳实践(工作常用)
8.1 能预判的异常,优先if判断
能用 if 避免空指针,就不要靠 try-catch 兜底
try-catch 开销更大
8.2 禁止空捕获(空 catch)
// 极其不推荐!吞异常!
try{
}catch(Exception e){
}
8.3 异常必须打印日志
线上排查问题唯一依据
log.error("用户登录失败",e);
8.4 尽量使用运行时异常
自定义异常继承 RuntimeException
不用频繁写 throws,代码更干净
九、全文总结
-
Java 异常体系核心分为 Error、编译异常、运行异常
-
运行异常是代码不严谨导致,可逻辑规避
-
五大关键字:try/catch/finally/throw/throws 各司其职
-
项目中必须自定义业务异常区分业务错误
-
异常处理原则:精准捕获、日志留存、不吞异常、优先预判
异常是 Java 稳定性的核心,搞懂异常体系,不仅能解决面试题,更能写出健壮、线上少崩的代码。
浙公网安备 33010602011771号