当前位置: 首页 > backend >正文

编程语言Java——核心技术篇(三)异常处理详解

  续前篇:编程语言Java——核心技术篇(二)类的高级特性-CSDN博客

目录

3. 异常处理

3.1 异常的层次和结构

3.2 异常的本质与设计

3.2.1 异常的理论基础

3.2.2 Java异常的设计考量

3.2.3 异常与返回值的区别

3.3 异常类体系解析

3.3.1 Throwable:异常体系的根基

3.3.2 Error:不可恢复的严重问题

3.3.3 Exception:可处理的异常

3.4 异常类的关键方法 

3.4.1 异常类的构造方法

3.4.2 异常链操作方法

3.4.3 栈轨迹相关方法

 3.5 自定义异常

3.5.1 自定义异常必要性分析

3.5.2 自定义异常实战示例

3.5.2.1 BusinessException 实现

3.5.2.2 OrderException 实现

ps. 自动日志输出

3.5.2.3 TechnicalException 实现

3.5.2.4 DatabaseException 实现

3.6 总结


 

3. 异常处理

异常处理是Java编程中非常重要的部分,它允许程序在遇到错误时优雅地处理问题,而不是直接崩溃。

3.1 异常的层次和结构

 由上面这个图我们就可以看出,实际上Java在处理异常时的超类是Throwable,他有两个子类分别是Error错误和Exception异常。

错误(Error)通常是指严重问题,应用程序通常不处理;

例如:OutOfMemoryError, StackOverflowError

而异常又分为运行时异常和编译时异常:

1. 检查型异常(Checked Exception)是指程序在编译阶段就被发现的异常,它必须被捕获或声明抛出,继承自Exception但不继承RuntimeException

例如:IOException, SQLException;

举个例子:

这就是典型的检查型异常,程序在编译的时候就已经报错,且不解决无法运行程序。

2. 运行时异常(Runtime Exception) 则强调的是Java在运行时抛出的异常。它不强制要求捕获或声明,在编译时也不报错,只有运行时才会抛出。

例如:NullPointerException, ArrayIndexOutOfBoundsException

举个例子:

很明显,我创建的数组在输出时越界了,而这样的错误只有在运行后才抛出,编译时并不提示。

3. Error vs Exception关键区别

特性ErrorException
可恢复性不可恢复(JVM级问题)可能恢复
是否需要处理不建议捕获处理必须处理(检查型异常)
典型示例OutOfMemoryErrorIOException
继承体系extends Throwableextends Throwable

 

3.2 异常的本质与设计

异常处理机制是Java语言设计中最为精妙的部分之一,它体现了"问题导向"的编程思想。我们可以从计算机科学的角度深入理解异常的本质:

3.2.1 异常的理论基础

在计算机程序执行过程中,存在两种基本状态:

  • 正常控制流:按照代码顺序逐行执行

  • 异常控制流:当检测到异常条件时,程序执行路径发生强制性跳转

Java的异常机制实际上是一种结构化的"非本地跳转"实现,与C语言的setjmp/longjmp机制类似,但更加安全和面向对象。每次异常抛出时,JVM会执行以下操作:

  1. 暂停当前方法执行

  2. 从当前栈帧开始逐层回溯调用栈

  3. 查找匹配的异常处理器(catch块)

  4. 如果找到则移交控制权,否则线程终止

3.2.2 Java异常的设计考量

Java语言设计团队在异常系统设计中做出了几个关键决策:

1. 检查型与非检查型异常分离

  • 检查型异常(Checked Exception):必须捕获或声明抛出

    • 代表可预期的外部问题(如文件不存在)

    • 编译器强制检查,确保可靠性

  • 非检查型异常(Unchecked Exception):无需声明

    • 代表程序错误(如空指针)

    • 通过代码质量避免而非处理

2. 异常链式设计

try {
    // 可能抛出低层异常
} catch (LowLevelException e) {
    throw new HighLevelException("上下文信息", e);
    // 保留原始异常信息
}

这种设计保留了完整的错误溯源能力,通过e.getCause()可以获取底层异常。

3. 栈轨迹保留

每个异常对象都会自动捕获完整的调用栈信息(可通过printStackTrace()输出),这在调试时非常有用,但也带来了性能开销。

3.2.3 异常与返回值的区别

1. 返回值(Return Value)

定义:通过方法的返回值(如整数、布尔值、对象等)表示操作结果或错误状态。

特点:

(1)显式处理:调用方必须主动检查返回值才能知道是否出错。

(2)紧耦合:错误逻辑和正常逻辑混合在代码中(例如通过 if-else 判断)。

(3)简单场景适用:适合预期内的、可恢复的简单错误(如参数校验失败)。

示例:

// 通过返回值表示错误(返回-1表示失败)
public int divide(int a, int b) {if (b == 0) {return -1; // 用特殊值表示错误}return a / b;
}// 调用方需要检查返回值
int result = divide(10, 0);
if (result == -1) {System.out.println("除数不能为0!");
}

2. 异常(Exception)

定义:通过抛出异常对象(如 throw new Exception("error"))中断正常流程,由 try-catch 块捕获处理。

特点:

(1)隐式处理:错误通过异常机制自动传播,无需逐层检查返回值。

(2)解耦:错误处理逻辑与业务逻辑分离(集中在 catch 块中)。

(3)复杂场景适用:适合不可预期、严重的或跨多层调用的错误(如文件不存在、网络断开)。

示例:

// 通过异常表示错误
public int divide(int a, int b) throws IllegalArgumentException {if (b == 0) {throw new IllegalArgumentException("除数不能为0!");}return a / b;
}// 调用方通过try-catch处理
try {int result = divide(10, 0);
} catch (IllegalArgumentException e) {System.out.println(e.getMessage()); // 输出错误信息
}

3.核心区别总结

维度返回值异常
反馈方式通过方法返回特殊值(如 -1null通过抛出异常对象(如 throw new Exception()
流程控制需手动检查返回值(if-else自动跳转到最近的 catch 块
代码可读性错误处理与业务逻辑混合错误处理集中,业务逻辑更清晰
适用场景预期内的简单错误(如参数校验)不可预期的复杂错误(如系统故障、外部依赖失败)
性能开销无额外开销抛出异常会生成栈跟踪,性能开销较大

3.3 异常类体系解析

3.3.1 Throwable:异常体系的根基

Throwable类是Java异常体系的根类,位于java.lang包中,是所有错误和异常的父类。它的核心设计包含以下关键特性:

1. 错误信息存储:

detailMessage字段存储异常描述信息

通过构造方法设置:Throwable(String message)

2. 异常链支持:

cause字段保存引发当前异常的原始异常

可通过initCause(Throwable cause)方法设置

3. 栈轨迹记录:

stackTrace数组保存方法调用栈的快照

fillInStackTrace()方法负责填充栈信息(native实现)

4. 抑制异常机制:

Java 7引入的suppressedExceptions列表

用于try-with-resources语句中处理多个异常

public class Throwable implements Serializable {// 序列化版本号private static final long serialVersionUID = -3042686055658047285L;// 异常详细信息private String detailMessage;// 原因异常(构成异常链)private Throwable cause = this;// 栈轨迹数组private StackTraceElement[] stackTrace;// 被抑制的异常列表private List<Throwable> suppressedExceptions = Collections.emptyList();// 构造方法public Throwable() {fillInStackTrace();}// 关键方法public synchronized Throwable fillInStackTrace() {// native实现}public void printStackTrace() {// 打印栈轨迹}
}

3.3.2 Error:不可恢复的严重问题

Error及其子类表示JVM无法处理的严重问题,通常不应被应用程序捕获。主要分为以下几类:

1. 虚拟机错误(VirtualMachineError)

错误类型触发场景处理建议
OutOfMemoryError堆内存耗尽增加堆内存/优化内存使用
StackOverflowError递归调用过深检查递归终止条件
InternalErrorJVM内部错误升级JVM/报告Bug

2. 链接错误(LinkageError )

 

3.3.3 Exception:可处理的异常

Exception体系分为检查型异常和非检查型异常两大类:

1. 检查型异常(Checked Exception)

特点:

  • 继承自Exception但不继承RuntimeException

  • 必须被捕获或在方法签名中声明

  • 代表可预期的外部问题

常见检查型异常:

异常类型

常见场景

处理方法

IOException

文件/网络I/O操作失败

关闭资源/重试/通知用户

SQLException

数据库操作失败

回滚事务/记录日志

ClassNotFoundException

类加载失败

检查类路径/动态加载替代类

TimeoutException

操作超时

设置更合理的超时时间/重试

2. 非检查型异常(Unchecked Exception)

特点:

  1. 继承自RuntimeException

  2. 不强制要求捕获或声明

  3. 通常代表编程错误

RuntimeException主要子类:

// 空指针异常
public class NullPointerException extends RuntimeException {// Java 14增强的错误信息public String getMessage() {return String.format("Cannot invoke \"%s\" because \"%s\" is null",getStackTrace()[0].getMethodName(),getStackTrace()[0].getFileName());}
}// 非法参数异常
public class IllegalArgumentException extends RuntimeException {// 常用于参数校验public static <T> T requireNonNull(T obj, String message) {if (obj == null)throw new IllegalArgumentException(message);return obj;}
}// 并发修改异常
public class ConcurrentModificationException extends RuntimeException {// 快速失败(fail-fast)机制的实现基础
}

3.4 异常类的关键方法 

3.4.1 异常类的构造方法

// 自定义异常类
public class FileParseException extends IOException {// 无参构造:public BusinessException() {super();  // 显式调用父类Exception的无参构造方法}// 场景1:仅提供错误消息public FileParseException(String message) {super(message);  // 调用IOException(String)}// 场景2:包装底层异常public FileParseException(Throwable cause) {super(cause);  // 调用IOException(Throwable)}// 场景3:完整信息public FileParseException(String message, Throwable cause) {super(message, cause);  // 调用IOException(String, Throwable)}
}

3.4.2 异常链操作方法

// 设置原因异常
public synchronized Throwable initCause(Throwable cause) {if (this.cause != this)throw new IllegalStateException("Can't overwrite cause");if (cause == this)throw new IllegalArgumentException("Self-causation not permitted");this.cause = cause;return this;
}// 获取原因异常
public Throwable getCause() {return (cause == this ? null : cause);
}

3.4.3 栈轨迹相关方法

// 获取栈轨迹数组
public StackTraceElement[] getStackTrace() {return stackTrace.clone();
}// 设置栈轨迹
public void setStackTrace(StackTraceElement[] stackTrace) {// 验证和复制数组
}// 打印栈轨迹
public void printStackTrace() {printStackTrace(System.err);
}public void printStackTrace(PrintStream s) {// 实现细节...
}

 3.5 自定义异常

3.5.1 自定义异常必要性分析

自定义异常是Java异常体系的重要扩展,在以下场景中尤为必要:

(1)业务异常分类:将系统错误按业务域细分(如订单异常、支付异常、用户异常等)

(2)增强异常信息:携带业务上下文数据(订单ID、用户账号等)

(3)统一异常处理:为不同异常类型提供标准化处理方式

(4)异常监控:通过自定义字段支持更精细的错误统计和分析

3.5.2 自定义异常实战示例

首先明确我们要确定异常的继承体系。我们创建的是检查订单和支付时的运行时异常和后台数据库、后台系统集成过程时的自定义异常。图中业务异常和技术异常都是运行时异常,在运行时抛出。因为在写数据库异常DatabaseException时我们可能需要去判断SQL语句是否有编译错误,所以可能还会用到SQLException,在写IntegrationException也是同理,注意他们时虚线连接的,也就说他们的关系并非继承,只是实现/包装/转换关系。

3.5.2.1 BusinessException 实现

/*** 业务异常基类(所有自定义业务异常的父类)*/
public abstract class BusinessException extends RuntimeException {private final String errorCode;private final Instant timestamp;private final Map<String, Object> context = new HashMap<>();public BusinessException(String errorCode, String message) {super(message);this.errorCode = errorCode;this.timestamp = Instant.now();}public BusinessException(String errorCode, String message, Throwable cause) {super(message, cause);this.errorCode = errorCode;this.timestamp = Instant.now();}// 添加上下文信息public BusinessException withContext(String key, Object value) {context.put(key, value);return this;}// 获取错误码(供监控系统使用)public String getErrorCode() {return errorCode;}// 获取完整错误信息(含上下文)@Overridepublic String getMessage() {return String.format("[%s]%s Context: %s", errorCode, super.getMessage(), context.isEmpty() ? "none" : context);}// 获取机器可读的错误详情(用于API响应)public Map<String, Object> getErrorDetail() {Map<String, Object> detail = new LinkedHashMap<>();detail.put("code", errorCode);detail.put("message", super.getMessage());detail.put("timestamp", timestamp);detail.put("context", new HashMap<>(context));if (getCause() != null) {detail.put("cause", getCause().getClass().getSimpleName());}return detail;}
}

3.5.2.2 OrderException 实现

/*** 订单业务异常*/
public class OrderException extends BusinessException {public enum ErrorCode {ORDER_NOT_FOUND("ORDER-404", "订单不存在"),INVALID_STATUS("ORDER-400", "订单状态非法"),PAYMENT_FAILED("ORDER-500", "支付失败");private final String code;private final String defaultMsg;ErrorCode(String code, String defaultMsg) {this.code = code;this.defaultMsg = defaultMsg;}}private final String orderId;public OrderException(String orderId, ErrorCode errorCode) {super(errorCode.code, errorCode.defaultMsg);this.orderId = orderId;withContext("orderId", orderId);}public OrderException(String orderId, ErrorCode errorCode, String customMessage) {super(errorCode.code, customMessage);this.orderId = orderId;withContext("orderId", orderId);}public OrderException(String orderId, ErrorCode errorCode, Throwable cause) {super(errorCode.code, errorCode.defaultMsg, cause);this.orderId = orderId;withContext("orderId", orderId);}// 快捷创建方法public static OrderException notFound(String orderId) {return new OrderException(orderId, ErrorCode.ORDER_NOT_FOUND);}public static OrderException invalidStatus(String orderId, String currentStatus) {return new OrderException(orderId, ErrorCode.INVALID_STATUS).withContext("currentStatus", currentStatus);}
}

这里仅展示订单异常,因为支付异常也是同理。

这里如果我们如果还要细分,还可以写验证失败异常等...不过自动记录日志还要添加的,这样方便后续出现错误了之后把日志输出,便于溯源。

ps. 自动日志输出

public abstract class LoggableException extends RuntimeException {public LoggableException(String message) {super(message);logException();}public LoggableException(String message, Throwable cause) {super(message, cause);logException();}private void logException() {if (shouldLog()) {LoggerFactory.getLogger(getClass()).error(buildLogMessage(), this);}}protected boolean shouldLog() {return true;}protected String buildLogMessage() {return String.format("[%s] %s", getClass().getSimpleName(), getMessage());}
}

3.5.2.3 TechnicalException 实现

/*** 技术异常基类(所有非业务的技术异常父类)* 设计要点:* 1. 继承RuntimeException避免强制捕获* 2. 包含技术错误码和原始异常* 3. 支持自动日志记录*/
public class TechnicalException extends RuntimeException {private final String errorCode;private final Instant timestamp;private final Map<String, Object> techDetails = new HashMap<>();// 基础构造方法public TechnicalException(String errorCode, String message) {super(message);this.errorCode = errorCode;this.timestamp = Instant.now();logException(); // 构造时自动记录}// 包含原因异常的构造方法public TechnicalException(String errorCode, String message, Throwable cause) {super(message, cause);this.errorCode = errorCode;this.timestamp = Instant.now();logException();}// 添加上下文技术细节(链式调用)public TechnicalException withDetail(String key, Object value) {techDetails.put(key, value);return this;}// 自动记录日志(可重写)protected void logException() {LoggerFactory.getLogger(getClass()).error("[{}] {} - Details: {}",errorCode, getMessage(), techDetails,this // 传递异常对象以输出堆栈);}// 生成机器可读的错误信息public Map<String, Object> toErrorResponse() {Map<String, Object> response = new LinkedHashMap<>();response.put("errorType", "TECHNICAL_ERROR");response.put("code", errorCode);response.put("message", getMessage());response.put("timestamp", timestamp);response.put("details", new HashMap<>(techDetails));if (getCause() != null) {response.put("rootCause", getCause().getClass().getSimpleName());}return response;}// 静态工厂方法public static TechnicalException of(String errorCode, String message) {return new TechnicalException(errorCode, message);}
}

3.5.2.4 DatabaseException 实现

/*** 数据库异常(TechnicalException的子类)* 特色功能:* 1. 自动提取SQL状态码* 2. 区分连接异常与查询异常* 3. 支持批量操作错误*/
public class DatabaseException extends TechnicalException {public enum ErrorType {CONNECTION_FAILURE("DB-100"),QUERY_FAILURE("DB-200"),TRANSACTION_FAILURE("DB-300"),TIMEOUT("DB-400");private final String codePrefix;ErrorType(String codePrefix) {this.codePrefix = codePrefix;}}private final String sqlState;private final ErrorType errorType;// 基础构造public DatabaseException(ErrorType errorType, String sqlState, String message) {super(generateCode(errorType, sqlState), "数据库错误: " + message);this.sqlState = sqlState;this.errorType = errorType;}// 从JDBC异常转换public static DatabaseException from(SQLException e) {ErrorType type = classify(e);return new DatabaseException(type,e.getSQLState(),e.getMessage()).withDetail("vendorCode", e.getErrorCode()).withDetail("batchPosition", e instanceof BatchUpdateException ? ((BatchUpdateException)e).getUpdateCounts().length : null);}// 错误分类逻辑private static ErrorType classify(SQLException e) {// 08xxx 是连接错误(SQL标准状态码)return (e.getSQLState() != null && e.getSQLState().startsWith("08")) ? ErrorType.CONNECTION_FAILURE : ErrorType.QUERY_FAILURE;}// 生成复合错误码private static String generateCode(ErrorType type, String sqlState) {return type.codePrefix + (sqlState != null ? "-" + sqlState : "");}// 获取原始SQL状态码public String getSqlState() {return sqlState;}// 重写日志记录(数据库错误单独记录)@Overrideprotected void logException() {LoggerFactory.getLogger("SQL_ERROR").error("Database failure [{}] - State: {} - Details: {}", getErrorCode(), sqlState, techDetails, this);}
}

注意到了吗,这里就用到了SQLException,用于检查是否出现连接错误!!

注意:这里只展示了一部分代码,我在BusinessException和TechnicalException都只实现了一个方法作为示例。

使用方法:

try (Connection conn = dataSource.getConnection();PreparedStatement stmt = conn.prepareStatement(sql)) {stmt.executeUpdate();
} catch (SQLException e) {throw DatabaseException.from(e).withDetail("sql", sql).withDetail("params", params);
}

不过还是要用try-catch方法去捕获,如果会Spring的话只会更简便。

3.6 总结

异常其实没什么内容,只不过我加的这个示例太长了可能感觉比较复杂。了解异常继承体系,在自定义异常时能够分辨出就够了,其余代码素养还需要勤加练习。

 

http://www.xdnf.cn/news/16261.html

相关文章:

  • Springboot+activiti启动时报错XMLException: Error reading XML
  • 深度学习day02--神经网络(前三节)
  • Elasticsearch-8.17.0 centos7安装
  • Ubuntu 环境下创建并启动一个 MediaMTX 的 systemd 服务
  • 栈与队列:数据结构核心解密
  • 链表反转算法详解
  • Fluent自动化仿真(TUI命令脚本教程)
  • springboot(3.4.8)整合mybatis
  • 【图像理解进阶】如何对图像中的小区域进行细粒度的语义分割?
  • WAIC2025预告|英码深元AI一体机将亮相华为昇腾展区,以灵活部署的能力赋能行业智能化转型
  • Nginx简单介绍
  • Java-Properties类和properties文件详解
  • 图论:最小生成树
  • classgraph:Java轻量级类和包扫描器
  • linux C — udp,tcp通信
  • 【Chrome】下载chromedriver的地址
  • 深入解析浏览器存储方案:Cookie、localStorage和sessionStorage特性与应用
  • GPU 服务器ecc报错处理
  • Java排序算法之<冒泡排序>
  • 单片机(STM32-ADC模数转换器)
  • 优思学院|QC七大手法之一的检查表应如何有效使用?
  • CSS 盒子模型学习版的理解
  • 数据结构 二叉树(1)
  • yarn在macOS上的安装与镜像源配置:全方位指南
  • 从 SQL Server 到 KingbaseES V9R4C12,一次“无痛”迁移与深度兼容体验实录
  • Orbbec开发---数据流与数据流操作
  • ZLMediaKit 源代码入门
  • Spring 策略模式实现
  • 【DeepRare】疾病识别召回率100%
  • SpringBoot学习路径二--Spring Boot自动配置原理深度解析