Java 中异常的类型划分 、每种类型的用法 / 特点 ,以及实际开发中的注意事项,我会把这些内容梳理得清晰易懂,结合新手容易踩的坑,帮你建立完整的异常知识体系。
一、异常的核心分类(两大体系)
Java 中所有异常都继承自 Throwable 类,核心分为两大类型(用通俗的比喻 + 技术定义讲清楚):

1. 第一类:Error(错误)------ 程序 "治不了的病"
- 通俗理解:相当于程序得了 "绝症"(比如硬件故障、JVM 崩溃),开发者无法通过代码处理,只能提前规避。
- 常见例子 :
OutOfMemoryError:内存溢出(比如无限创建对象,把堆内存撑爆);StackOverflowError:栈溢出(比如递归调用没有终止条件);NoClassDefFoundError:找不到类定义(比如依赖的 jar 包缺失)。
- 特点 :
- 继承自
Throwable,但不属于Exception; - 不需要(也无法)用
try-catch捕获,捕获了也处理不了; - 出现时 JVM 通常会直接终止程序。
- 继承自
- 注意点:开发中要通过优化代码(比如避免无限递归)、配置 JVM 参数(比如扩大内存)来预防,而非捕获处理。
2. 第二类:Exception(异常)------ 程序 "能治好的病"
这是开发者重点处理的异常,又分为受检异常 和非受检异常:
| 类型 | 通俗理解 | 核心特点 | 常见例子 |
|---|---|---|---|
| 受检异常(Checked) | "编译时就催你处理的异常" | 编译阶段必须处理(try-catch 或 throws),否则代码报错 |
IOException(IO 操作)、SQLException(数据库操作)、ClassNotFoundException(类找不到) |
| 非受检异常(Unchecked) | "运行时才暴露的异常" | 编译阶段不强制处理,运行时才会触发 | 所有 RuntimeException 子类:NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组越界)、IllegalArgumentException(参数非法) |
二、不同类型异常的用法 & 特点(结合场景)
1. 受检异常(Checked Exception)
-
用法 :必须显式处理,二选一:
- 用
try-catch捕获并处理; - 用
throws声明,把处理责任交给调用者。
- 用
-
典型场景:和 "外部资源交互" 的操作(读写文件、访问数据库、调用网络接口),因为这些操作的失败是 "可预见但不可控" 的(比如文件可能不存在、网络可能断开)。
-
示例代码 :
java
运行
// 方式1:try-catch 处理 public void readFile() { try { FileReader reader = new FileReader("test.txt"); // 编译时提示必须处理IOException reader.close(); } catch (IOException e) { System.out.println("处理IO异常:" + e.getMessage()); } } // 方式2:throws 声明 public void readFile() throws IOException { // 声明抛异常,交给调用者处理 FileReader reader = new FileReader("test.txt"); reader.close(); } -
特点:强制开发者提前考虑异常场景,代码更健壮,但会增加代码冗余。
-
注意点 :不要滥用
throws一直往上抛(比如抛到main方法),最终一定要有一层代码做实际处理(比如提示用户、记录日志)。
2. 非受检异常(RuntimeException 及其子类)
-
用法 :编译时不强制处理,可处理也可不处理;但实际开发中建议预判并规避,而非事后捕获。
-
典型场景 :代码逻辑错误(比如调用
null对象的方法、数组下标越界、参数传错)。 -
示例代码 :
java
运行
public void testRuntimeException() { String str = null; // 运行时才会抛 NullPointerException,编译时不报错 System.out.println(str.length()); } -
特点 :
- 大多是开发者的代码逻辑问题导致,而非外部环境问题;
- 捕获这类异常不如提前规避(比如判空、校验参数)。
-
注意点 :❌ 不要用
try-catch掩盖逻辑错误(比如捕获NullPointerException却不修复空指针问题);✅ 提前预判:比如调用对象方法前先判空if (str != null) { ... }。
三、异常使用的核心注意事项(避坑指南)
1. 捕获异常的注意点
-
**① 不要捕获大范围异常(比如直接捕获
Exception/Throwable)**错误示例:java
运行
try { // 既有IO操作,又有字符串操作 FileReader reader = new FileReader("test.txt"); String str = null; str.length(); } catch (Exception e) { // 一把抓所有异常,无法精准定位问题 System.out.println("出错了"); }正确示例:按异常类型细分捕获,先抓子类再抓父类:
java
运行
try { FileReader reader = new FileReader("test.txt"); String str = null; str.length(); } catch (NullPointerException e) { // 先抓子类异常 System.out.println("空指针:" + e.getMessage()); } catch (IOException e) { // 再抓父类异常 System.out.println("IO错误:" + e.getMessage()); } -
**② 不要空捕获(catch 块里什么都不做)**错误示例:
java
运行
try { str.length(); } catch (NullPointerException e) { // 空捕获:异常被"吞掉",出问题时无法定位 }正确示例:至少记录日志或打印异常信息:
java
运行
catch (NullPointerException e) { System.out.println("空指针异常:" + e.getMessage()); e.printStackTrace(); // 开发阶段打印堆栈,方便定位 }
2. 抛出异常的注意点
-
① 抛具体的异常类型,而非通用类型错误示例:
java
运行
public void checkAge(int age) { if (age < 0) { throw new Exception("年龄非法"); // 抛通用Exception,调用者无法精准处理 } }正确示例:抛具体的运行时异常:
java
运行
public void checkAge(int age) { if (age < 0) { throw new IllegalArgumentException("年龄不能为负数:" + age); // 精准异常 } } -
② 异常信息要具体,便于定位问题 错误示例:
throw new IOException("出错了");(信息模糊)正确示例:throw new IOException("读取文件失败,路径:test.txt,原因:文件不存在");(信息具体)
3. finally 块的注意点
-
① finally 块不要返回值 :会覆盖 try/catch 块的返回值,导致逻辑混乱。错误示例:
java
运行
public int testFinally() { try { return 1; } catch (Exception e) { return 2; } finally { return 3; // 覆盖前面的返回值,最终返回3 } } -
② finally 块只做资源释放:不要在 finally 里写业务逻辑,避免代码混乱。
4. 自定义异常的注意点
如果系统自带的异常不贴合业务(比如 "用户不存在""余额不足"),可以自定义异常:
- 自定义受检异常:继承
Exception; - 自定义非受检异常:继承
RuntimeException(推荐,减少代码冗余)。示例代码:
java
运行
// 自定义业务异常(非受检)
class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message); // 传递异常信息
}
}
// 使用自定义异常
public void withdraw(double money) {
if (money > getBalance()) {
throw new InsufficientBalanceException("余额不足,当前余额:" + getBalance());
}
}
总结
- 异常类型核心划分:Error(无法处理,只能预防)、Exception(可处理);Exception 又分受检(编译强制处理)、非受检(运行时触发,优先规避);
- 用法原则 :受检异常(IO / 数据库)用
try-catch/throws处理,非受检异常(空指针 / 数组越界)提前预判规避; - 核心注意点:捕获异常要精准、不吞异常、异常信息要具体、finally 只做资源释放、自定义异常贴合业务。
简单记:异常处理的核心是 "精准捕获、合理抛出、提前规避",最终目的是让程序更健壮,而非掩盖问题。