Java异常:认识异常、异常的作用、自定义异常
目录
- 1.什么是异常?
- 1)运行时异常
- 2)编译时异常
- 2.异常的作用
- 1)Java 异常在定位 BUG 中的核心作用
- 2)Java 异常作为方法内部特殊返回值的作用
- 3)自定义异常
1.什么是异常?
Error:代表的系统级别错误(属于严重问题)
Exception:异常,代表的才是我们程序可能出现的问题,通常会用Exception以及它的子类来封装程序出现的问题
运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常),通常因为编写的代码有问题导致
编译时异常:编译阶段就会出现错误提醒的(如:日期解析异常),通常是Java官方对认为容易出问题的地方进行异常提醒,需要抛出异常或捕获异常,对异常进行处理
1)运行时异常
int[] arr = {1,2,3};
System.out.println(arr[3]);
执行后报数组越界异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3at excepttion.Test.main(Test.java:6)System.out.println(10/0);
执行后报运算异常:
Exception in thread "main" java.lang.ArithmeticException: / by zeroat excepttion.Test.main(Test.java:5)String s = null;
System.out.println(s.length());
执行后报空指针异常:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is nullat excepttion.Test.main(Test.java:6)
2)编译时异常
案例:处理异常可在方法名后使用throws关键字将异常抛给方法调用者处理或使用try…catch捕获并抛出异常
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class Test {public static void main(String[] args) {String str = "2025-05-06";//存在编译异常的方法调用时需使用try-catch块处理异常或在main方法中使用throws关键字try {StringToDate(str); //调用方法转换字符串为日期} catch (ParseException e) { //当字符串内容的格式不符合要求时(yyyy-MM-dd),会抛出ParseException异常e.printStackTrace(); //打印异常信息}}//定义转换字符串为日期的方法并使用throws关键字抛出异常给调用者处理public static void StringToDate(String str) throws ParseException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//调用parse方法将字符串转换成日期对象//此处出现编译异常的原因:输入的字符串格式和可能和SimpleDateFormat的格式不一致//例如需要输入2019/09/09,但SimpleDateFormat的格式是yyyy-MM-dd//因此此处会抛出ParseException,解决方式:使用try...catch或者throws关键字Date date = sdf.parse(str);System.out.println(date);}
}
案例:存在多个编译时异常可使用Exception接所有类型的异常
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class Test2 {public static void main(String[] args) {//使用try-catch块处理异常try {StringToDate();} catch (Exception e) {e.printStackTrace();}}//使用Exception接收所有异常public static void StringToDate() throws Exception {String str = "2025-05-06";SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//出现ParseException异常Date date = sdf.parse(str);System.out.println(date);//创建读取文件对象,抛出FileNotFoundException异常FileReader fr = new FileReader("d:/a.txt");}
}
2.异常的作用
Java 异常的作用分析:定位 BUG 与作为方法内部特殊返回值
1)Java 异常在定位 BUG 中的核心作用
Java 异常机制的核心价值之一是提供精准的错误定位能力,其通过异常堆栈跟踪(Stack Trace)实现对程序运行时错误的可视化追踪。当程序抛出未捕获的异常时,JVM 会自动生成包含异常类型、发生位置及调用链路的堆栈信息,帮助开发者快速定位问题根源。
①异常堆栈跟踪的信息构成
异常堆栈跟踪是定位 BUG 的 “导航图”,其结构包含三部分关键信息:
异常类型与消息:首行显示异常的全限定类名(如java.lang.NullPointerException)及异常消息(如Cannot invoke “String.length()” because “s” is null),直接揭示错误类别及触发原因;
堆栈帧序列:后续每行对应一个栈帧(StackTraceElement),包含异常传播路径中的调用信息,具体格式为at 类名.方法名(文件名:行号),其中行号是定位具体代码位置的关键。
例如,执行以下代码时:
public class Bug定位示例 {public static void main(String[] args) {String str = null;printLength(str); // 第4行:调用printLength方法}private static void printLength(String s) {System.out.println(s.length()); // 第8行:尝试调用null对象的length()方法}
}
会抛出NullPointerException,其堆栈跟踪信息如下:
java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is nullat Bug定位示例.printLength(Bug定位示例.java:8) // 异常发生点:第8行at Bug定位示例.main(Bug定位示例.java:4) // 调用链路:main方法第4行触发
②基于堆栈跟踪的 BUG 定位流程
开发者可通过以下步骤定位 BUG:
识别异常类型:通过异常类名判断错误类别(如NullPointerException表示空指针访问,IndexOutOfBoundsException表示数组越界),缩小排查范围;
解析异常消息:异常消息通常包含直接原因(如上述示例中s is null),可快速定位变量状态异常;
追踪堆栈帧链路:堆栈帧从下到上为调用链(最下方是异常发生点,上方是调用方),结合行号直接定位到具体代码行(如示例中的第 8 行s.length()),进而检查变量赋值、方法参数传递等上下文逻辑,最终找到根本原因(如 main 方法中str被赋值为null)。
③实战价值:减少 “盲猜式” 调试
传统调试依赖日志打印或断点调试,需手动排查代码执行路径;而异常堆栈跟踪直接标记错误发生的精确位置及传播路径,避免开发者在海量代码中 “盲猜”。例如,在多层调用的复杂系统中(如 Controller→Service→DAO),异常堆栈可直接显示异常是在 DAO 层 SQL 执行时抛出,还是在 Service 层参数校验时触发,大幅缩短定位时间。
2)Java 异常作为方法内部特殊返回值的作用
在方法设计中,除返回正常值外,常需传递 “特殊状态”(如文件不存在、参数非法、业务规则冲突等)。传统方案(如返回null、错误码)存在信息缺失或易被忽略的问题,而 Java 异常可作为结构化的 “特殊返回值”,高效传递异常状态并强制处理。
①传统特殊状态返回方案的局限性
错误码方案:如返回-1表示 “参数错误”、-2表示 “资源不存在”,需开发者主动检查返回值,若遗漏(如直接使用返回值参与运算),会导致错误传播。例如:
// 错误码方案示例(存在隐患)
public int divide(int a, int b) {if (b == 0) return -1; // 返回-1表示除数为0return a / b;
}
// 调用方可能忘记检查错误码,直接使用返回值:
int result = divide(5, 0);
System.out.println(result); // 输出-1,错误被掩盖
null返回方案:用null表示 “无结果”(如查询不到数据),但调用方若未判空直接操作(如调用null对象的方法),会触发NullPointerException,反而引入新错误。
②异常作为特殊返回值的优势
强制关注特殊状态:
受检异常(Checked Exception):如IOException、ClassNotFoundException,编译器强制要求调用方通过try-catch捕获或throws声明抛出,避免遗漏处理。例如,FileInputStream构造方法抛出FileNotFoundException(受检异常),强制开发者处理 “文件不存在” 的场景:
// 受检异常强制处理示例
public void readFile(String path) {try (FileInputStream fis = new FileInputStream(path)) { // 此处必须处理FileNotFoundException// 文件读取逻辑} catch (FileNotFoundException e) {System.err.println("文件不存在:" + path); // 显式处理特殊状态} catch (IOException e) {System.err.println("文件读取失败:" + e.getMessage());}
}
非受检异常(Unchecked Exception):如NullPointerException、IllegalArgumentException,虽不强制捕获,但通过异常类型和消息提醒开发者关注(如参数校验失败),比null或错误码更直观。
3)自定义异常
异常可封装比错误码 /null更详细的信息,包括:
异常类型:通过不同异常类区分特殊状态类别(如IllegalArgumentException表示参数错误,TimeoutException表示超时);
异常消息:自定义文本描述具体原因(如"用户ID=123不存在");
自定义字段:通过自定义异常类添加业务相关属性(如错误码、用户 ID、操作类型等)。
例如,自定义业务异常传递多维度信息:
// 自定义异常作为特殊返回值(携带业务上下文)
class UserNotFoundException extends Exception {private final int userId; // 携带异常相关的用户IDprivate final String errorCode; // 携带业务错误码public UserNotFoundException(String message, int userId, String errorCode) {super(message);this.userId = userId;this.errorCode = errorCode;}// Getter方法获取自定义字段public int getUserId() { return userId; }public String getErrorCode() { return errorCode; }
}// 方法中抛出自定义异常
public User getUserById(int id) throws UserNotFoundException {User user = userDao.query(id);if (user == null) {// 抛出异常时传递详细信息:消息、用户ID、错误码throw new UserNotFoundException("用户不存在", id, "USER_NOT_FOUND_001");}return user;
}// 调用方捕获异常并获取上下文
try {User user = getUserById(123);
} catch (UserNotFoundException e) {log.error("查询失败:{},用户ID:{},错误码:{}", e.getMessage(), e.getUserId(), e.getErrorCode()); // 输出:查询失败:用户不存在,用户ID:123,错误码:USER_NOT_FOUND_001
}
(3)中断错误流程,避免错误扩散
当方法遇到无法处理的特殊状态时,抛出异常可立即中断当前执行流程,阻止错误结果继续传播。例如,支付系统中若检测到 “余额不足”,抛出InsufficientBalanceException可终止后续扣款逻辑,避免资金异常。
三、总结
Java 异常机制在程序开发中具有双重核心价值:
定位 BUG:通过堆栈跟踪提供异常发生的精确位置(类、方法、行号)及调用链路,是高效调试的 “导航系统”;
作为特殊返回值:相比错误码 /null,异常通过强制处理机制(受检异常)、丰富上下文信息(类型、消息、自定义字段)及流程中断能力,更可靠地传递方法执行中的特殊状态,提升代码健壮性与可维护性。
合理使用异常机制,可显著降低调试成本,减少因特殊状态处理不当导致的系统故障。