最近复习Java知识, 异常我平时就刷刷算法题, 接触的不多都快忘完了, 所以就从初学的角度重新理解一下.
Java 异常机制入门学习笔记

一、什么是异常?
- 异常本质上是一个 "异常类的对象",所有异常都继承自Throwable类
- 当程序运行中出现特殊情况(如除数为 0、文件不存在等)时,会创建异常对象并抛出(throw)
-
异常机制是 Java 中处理程序错误的标准化方式,与 if-else 相比有明显优势:
-
相当于能设置监听器了, 在发生监听的异常事件后会直接抛出相应异常, if-else必须提前预判哪里会出现异常, 定义好监听器后是一劳永逸的, 同类错误发生后会自动抛出了
-
支持错误自动向上传播,简化多层方法调用中的错误处理
-
允许将错误交给更适合处理它的上层代码处理
-
二、异常的抛出与处理流程
1. 异常的手动抛出(throw)
- 使用throw关键字手动抛出异常对象:
php
if (b == 0) {
throw new RuntimeException("除数不能为0");
}
- 有手动抛出自然也有自动抛出, 那就是触发监听事件后就会自动抛出相应异常
不管手动抛出还是自动抛出, 抛出的异常都会像 "错误包裹" 一样,从发生地开始自动向上一层栈帧传递
2. 异常的三种处理方式
- 不处理:
-
异常会自动向上一层抛,沿着方法调用链逐层传递,直到被处理或传递到程序顶层main方法处
-
若一直未被处理,最终由 JVM 处理
-
表现为:程序崩溃,打印错误堆栈信息
- 当场捕获处理( try-catch ) :
Java
try {
// 可能抛出异常的代码
new Test2().test(1,0);
} catch (Exception e) {
// 异常处理逻辑
System.out.println("抓到了");
}
- 在可能抛出异常的代码块外包裹try,用catch捕获并处理
- 处理后程序可以继续执行,不会崩溃
- 主动向上传递( throws ) :
java
public void test(int a, int b) throws Exception {
if (b == 0) {
throw new Exception("除数不能为0");
}
}
- 在方法声明处用throws声明可能抛出的异常
- 表示 "当前方法不处理,交给上层调用者处理"
- 传递过程中会退出当前方法栈帧,后面的代码不再执行
实际上哪怕你不向上抛JVM也会主动向上抛, 只是有些异常不抛根本编译不过, 相当于这种异常是Java在提醒你一定要处理, 这时你可以选择当场捕获或者向上抛
三、什么异常必须抛(不用记, 了解即可)
1. 受检异常 vs 非受检异常
- 非受检异常:
-
- 继承自RuntimeException及其子类(如NullPointerException)
-
- 不需要在方法上用throws声明(编译器不强制)
-
- 通常是代码逻辑错误导致的,应通过改进代码避免
- 受检异常:
-
- 除了非受检异常之外的其他异常(如IOException、Exception)
-
- 必须在方法上用throws声明,否则编译错误
-
- 编译器强制要求处理(要么try-catch,要么throws传递)
2. throws关键字的作用
- 对受检异常:是 "强制声明",不声明会编译错误
- 对非受检异常:是 "友好提醒",告知调用者可能存在的风险
- 本质是告诉调用者:"我这个方法可能会抛出这些异常,你需要处理"
四、异常捕获的方法, 用大于等于的引用变量去接异常对象
-
可以用:
- 异常类本身的引用捕获(精确匹配)
- 异常类的父类引用捕获(大的接小的)
- 匹配顺序:
- 编译器从上到下顺序执行, 依次匹配catch块
- 一旦找到匹配的catch块,就执行该块代码,其他catch块被忽略
- 最佳实践:
csharp
try {
throw new IOException("文件错误");
} catch (IOException e) { // 精确匹配
System.out.println("处理IO错误");
} catch (Exception e) { // 父类匹配(如果前面没匹配才会执行)
System.out.println("处理通用错误");
}
- 具体异常的catch块放在前面
- 父类异常的catch块放在后面
- 捕获后可以通过异常对象的引用(如e)访问异常信息:
- e.getMessage():获取错误信息
- e.printStackTrace():打印完整错误堆栈
- 直接打印toString方法应该也可以直接看到错误信息
五、自定义异常(用的不多)
1. 自定义异常的创建
- 继承Exception(受检异常)或RuntimeException(非受检异常)
- 通常需要实现几个构造方法,方便传递错误信息
scala
// 自定义受检异常
class MyException extends Exception {
// 无参构造
public MyException() {
super();
}
// 带错误信息的构造
public MyException(String message) {
super(message);
// 给父类的私有成员变量传递错误信息, 打印的时候直接打印出message
}
// 带错误码的构造(自定义扩展)
public MyException(int code) {
super("错误码:" + code);
// 给父类的私有成员变量传递错误信息, 打印的时候直接打印出code
}
}
2. 自定义异常的使用
- 在适当的业务场景手动抛出
- 用try-catch捕获处理或用throws向上传递
csharp
public class Test {
public static void main(String[] args) {
try {
new Test().a();
} catch (MyException e) {
System.out.println(e); // 打印异常信息
}
}
private void a() throws MyException {
// 业务逻辑判断,满足条件时抛出自定义异常
throw new MyException(1001); // 抛出异常对象
}
}
3. 自定义异常的意义
- 区分不同业务逻辑错误(如UsernameExistsException、InsufficientBalanceException)
- 使错误处理更符合业务需求,代码可读性更高
六、关键概念总结
- 异常对象:异常本质是对象,包含错误信息
- 抛出与捕获:throw抛出异常,try-catch捕获处理
- 传递机制:异常可通过throws向上传递,直到被处理或导致程序崩溃
- 类型区分:受检异常必须声明处理,非受检异常可选
- 多态匹配:异常捕获支持父类引用匹配子类异常对象
- 自定义异常:继承异常父类,用于特定业务场景的错误处理