Java异常处理笔记

引言

在理想的世界中,程序永远不会出错。但现实是,用户输入错误、网络中断、磁盘空间不足......各种意外随时可能发生。Java异常处理机制为我们提供了一套优雅的方式来应对这些运行时问题,确保程序的健壮性和用户体验。

本文将全面讲解Java异常处理,包括异常体系结构、处理方式、常见陷阱以及最佳实践,帮助你写出更可靠的代码。

一、什么是异常?

异常(Exception)是程序运行过程中出现的非正常事件,它打断了指令的正常执行流程。Java通过面向对象的方式处理异常,每个异常都是一个对象,包含错误类型、状态和错误信息。

异常处理的主要优势:

  • 将错误处理代码与正常业务逻辑分离。

  • 提供清晰的错误传播方式。

  • 支持异常的分类管理和统一处理。

二、Java异常体系结构

Java中所有异常类的父类是Throwable,它有两个重要的子类:ErrorException

复制代码
java.lang.Object
    └── java.lang.Throwable
            ├── java.lang.Error
            └── java.lang.Exception
                    ├── RuntimeException(运行时异常)
                    └── 其他非运行时异常(受检异常)

1. Error(错误)

Error表示系统级错误,通常由JVM抛出,应用程序不应该尝试捕获。例如:

  • OutOfMemoryError:内存溢出

  • StackOverflowError:栈溢出

  • NoClassDefFoundError:类定义找不到

2. Exception(异常)

Exception是程序可以处理的异常情况,分为两类:

受检异常(Checked Exception)
  • RuntimeException及其子类以外的Exception

  • 编译器强制要求处理(捕获或声明抛出)。

  • 例如:IOExceptionSQLExceptionClassNotFoundException

非受检异常(Unchecked Exception)
  • RuntimeException及其子类。

  • 编译器不强制处理,通常由程序逻辑错误引起。

  • 例如:NullPointerExceptionIndexOutOfBoundsExceptionIllegalArgumentException

为什么这样设计?

  • 受检异常:表示可预见的、外部因素导致的异常(如文件不存在),调用者应该处理。

  • 非受检异常:表示程序内部错误(如空指针),通常通过改进代码避免,无需强制处理。

三、异常处理机制

1. try-catch-finally

java 复制代码
try {
    // 可能抛出异常的代码
} catch (IOException e) {
    // 处理特定类型的异常
} catch (Exception e) {
    // 处理其他异常(顺序重要:子类在前,父类在后)
} finally {
    // 无论是否发生异常都会执行(通常用于释放资源)
}
  • try:监控代码块,可能抛出异常。

  • catch:捕获并处理特定异常,可以有多个。

  • finally:可选,总是执行,常用于关闭资源(如文件流、数据库连接)。

2. try-with-resources(Java 7+)

自动关闭实现了AutoCloseable接口的资源,无需显式在finally中关闭。

java 复制代码
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    // 使用资源
} catch (IOException e) {
    e.printStackTrace();
}
// 资源自动关闭

3. throws 和 throw

  • throws:在方法签名中声明可能抛出的异常,告知调用者需要处理。

    java 复制代码
    public void readFile() throws IOException {
        // ...
    }
  • throw:手动抛出异常对象。

    java 复制代码
    if (age < 0) {
        throw new IllegalArgumentException("年龄不能为负数");
    }

四、常见异常举例

异常类型 常见场景
NullPointerException 调用空对象的方法或属性
ArrayIndexOutOfBoundsException 数组下标越界
ClassCastException 强制类型转换失败
IllegalArgumentException 方法参数不合法
NumberFormatException 字符串转数字失败
IOException 输入输出操作失败(如文件不存在、网络中断)
SQLException 数据库操作异常
ArithmeticException 算术异常(如除零)

五、自定义异常

当Java内置异常不足以描述特定业务问题时,可以创建自定义异常。

1. 自定义受检异常

继承Exception类:

java 复制代码
public class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

2. 自定义非受检异常

继承RuntimeException类:

java 复制代码
public class InvalidUserInputException extends RuntimeException {
    public InvalidUserInputException(String message) {
        super(message);
    }
}

3. 为自定义异常添加构造方法

通常提供无参、带消息、带原因(cause)的构造方法:

java 复制代码
public class BusinessException extends Exception {
    public BusinessException() {}
    public BusinessException(String message) { super(message); }
    public BusinessException(String message, Throwable cause) { super(message, cause); }
}

六、异常处理最佳实践

1. 合理选择异常类型

  • 如果调用者必须 处理异常,使用受检异常(如文件未找到)。

  • 如果异常是由编程错误引起,使用非受检异常(如空指针),避免代码过度try-catch。

2. 不要忽略异常

空catch块是万恶之源:

java 复制代码
try {
    // ...
} catch (Exception e) {
    // 什么也不做(异常被吞没,问题更难排查)
}

至少应该记录日志或抛出合理信息。

3. 记录异常日志

使用日志框架(如SLF4J + Logback)记录异常信息,包括堆栈跟踪。

java 复制代码
catch (IOException e) {
    log.error("文件读取失败", e);  // 正确:记录堆栈
    // throw new BusinessException("读取文件出错", e);  // 保留原始异常
}

4. 尽早抛出异常,延迟捕获异常

  • 发现错误应立即抛出,避免程序继续运行导致更严重后果。

  • 在能够处理异常的地方捕获,而不是在最底层捕获后什么都不做。

5. 不要使用异常控制正常流程

异常处理的性能开销较大,不应替代常规条件判断(如用NumberFormatException判断字符串是否为数字)。

6. 保持异常的原子性

当方法执行失败时,应使对象状态回滚到调用前的状态,避免部分修改。

7. 在finally中谨慎返回值

不要在finally块中使用return,它会覆盖try/catch中的返回值。同样,不要在finally中抛出异常,可能掩盖之前的异常。

8. 使用try-with-resources自动关闭资源

确保所有实现了AutoCloseable的资源都正确关闭。

9. 封装底层异常

将底层异常(如SQLException)转换为业务异常,避免暴露实现细节:

java 复制代码
catch (SQLException e) {
    throw new DataAccessException("数据库操作失败", e);
}

10. 文档化异常

使用Javadoc的@throws标记说明方法可能抛出的异常及其原因。

java 复制代码
/**
 * 根据ID查询用户
 * @param id 用户ID
 * @return 用户对象
 * @throws IllegalArgumentException 如果id为null或负数
 * @throws UserNotFoundException 如果用户不存在
 */

七、总结

Java异常处理是一把双刃剑:用得好,程序稳健、易于维护;用得不好,代码混乱、难以调试。掌握异常体系结构、处理语法和最佳实践,是成为优秀Java开发者的必经之路。

记住核心原则:

  • 受检异常用于可恢复的外部错误

  • 非受检异常用于程序内部错误

  • 永远不要吞没异常

  • 记录完整日志

  • 资源必须正确关闭

相关推荐
今儿敲了吗2 小时前
44| 汉诺塔问题
数据结构·c++·笔记·学习·算法·深度优先
黄嚯嚯3 小时前
从字段堆砌到类型建模:一个 PricingDetails 的重构实践
java·笔记
困死,根本不会3 小时前
蓝桥杯 Python 备考全攻略:从入门到进阶的学习路线
笔记·python·学习·算法·蓝桥杯
浅念-4 小时前
C++ 异常
开发语言·数据结构·数据库·c++·经验分享·笔记·学习
ycjunhua4 小时前
Gool NoteBookLM 创建无法进入开发界面
笔记·学习
今儿敲了吗5 小时前
python基础学习笔记第四章
c++·笔记·python·学习
【数据删除】3485 小时前
计算机复试学习笔记 Day44
笔记·学习
星轨初途5 小时前
类和对象(上)
开发语言·c++·经验分享·笔记
留院极客离心圆5 小时前
C++ 进阶笔记:宏
开发语言·c++·笔记