异常的分类与用法

Java 中异常的类型划分 、每种类型的用法 / 特点 ,以及实际开发中的注意事项,我会把这些内容梳理得清晰易懂,结合新手容易踩的坑,帮你建立完整的异常知识体系。

一、异常的核心分类(两大体系)

Java 中所有异常都继承自 Throwable 类,核心分为两大类型(用通俗的比喻 + 技术定义讲清楚):

1. 第一类:Error(错误)------ 程序 "治不了的病"
  • 通俗理解:相当于程序得了 "绝症"(比如硬件故障、JVM 崩溃),开发者无法通过代码处理,只能提前规避。
  • 常见例子
    • OutOfMemoryError:内存溢出(比如无限创建对象,把堆内存撑爆);
    • StackOverflowError:栈溢出(比如递归调用没有终止条件);
    • NoClassDefFoundError:找不到类定义(比如依赖的 jar 包缺失)。
  • 特点
    • 继承自 Throwable,但不属于 Exception
    • 不需要(也无法)用 try-catch 捕获,捕获了也处理不了;
    • 出现时 JVM 通常会直接终止程序。
  • 注意点:开发中要通过优化代码(比如避免无限递归)、配置 JVM 参数(比如扩大内存)来预防,而非捕获处理。
2. 第二类:Exception(异常)------ 程序 "能治好的病"

这是开发者重点处理的异常,又分为受检异常非受检异常

类型 通俗理解 核心特点 常见例子
受检异常(Checked) "编译时就催你处理的异常" 编译阶段必须处理(try-catchthrows),否则代码报错 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());
    }
}

总结

  1. 异常类型核心划分:Error(无法处理,只能预防)、Exception(可处理);Exception 又分受检(编译强制处理)、非受检(运行时触发,优先规避);
  2. 用法原则 :受检异常(IO / 数据库)用 try-catch/throws 处理,非受检异常(空指针 / 数组越界)提前预判规避;
  3. 核心注意点:捕获异常要精准、不吞异常、异常信息要具体、finally 只做资源释放、自定义异常贴合业务。

简单记:异常处理的核心是 "精准捕获、合理抛出、提前规避",最终目的是让程序更健壮,而非掩盖问题。

相关推荐
青云交5 小时前
Java 大视界 -- Java 大数据实战:分布式架构重构气象预警平台(2 小时→2 分钟)
java·java 大数据 气象预警平台·flink 实时数据清洗·spark 区域定制模型·气象灾害预警系统
布局呆星5 小时前
面向对象中的封装-继承-多态
开发语言·python
专注API从业者5 小时前
淘宝商品 API 接口架构解析:从请求到详情数据返回的完整链路
java·大数据·开发语言·数据库·架构
独自破碎E5 小时前
解释一下为什么要有虚拟内存
java
哪里不会点哪里.5 小时前
Spring 的装配顺序详解(配置 → 扫描 → 注入 → 初始化)
java·sql·spring
木千5 小时前
Qt全屏显示,在顶部工具栏的最右边显示关闭按钮
开发语言·qt
xiaolyuh1235 小时前
Spring MVC 深度解析
java·spring·mvc
-凌凌漆-5 小时前
【java】java中函数加与不加abstract 的区别
java·开发语言
你撅嘴真丑5 小时前
STL练习
开发语言·c++·算法