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开发者的必经之路。

记住核心原则:

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

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

  • 永远不要吞没异常

  • 记录完整日志

  • 资源必须正确关闭

相关推荐
疯狂打码的少年3 小时前
【数据结构】图的遍历:深度优先搜索(DFS)
数据结构·笔记·算法·深度优先
智者知已应修善业4 小时前
【51单片机数码管逐位显示1-8余位不亮】2024-10-23
c语言·经验分享·笔记·嵌入式硬件·51单片机
Wang's Blog4 小时前
PostgreSQL笔记16: 索引基础——概念、类型、创建与最佳实践
数据库·笔记·postgresql
ysa0510305 小时前
c++常用自带函数用法与注意
c++·笔记·算法
无聊的菜鸟5 小时前
TMS320F2806X学习笔记(一)—— 了解片上资源
笔记·ti·c2000·f2806x
优化Henry6 小时前
学习笔记之爱立信站点指向证书执行
运维·服务器·网络·笔记·学习·信息与通信
IT古董8 小时前
【WMS学习笔记系列】03-功能模块设计
笔记·学习·系统架构
vivo互联网技术10 小时前
协同文档下的 Agent 协作闭环:可回滚、可对比的透明化编辑实现
人工智能·笔记·agent
Wang's Blog10 小时前
PostgreSQL笔记27: 双缓存架构下的内存管理与性能观测实践
笔记·缓存·postgresql
Wang's Blog10 小时前
PostgreSQL笔记22: WAL日志与崩溃恢复原理
数据库·笔记·postgresql