一、异常
在编写和运行 Java 程序时,我们难免会遇到各种问题:比如文件找不到、数组下标越界、输入格式错误、业务逻辑不符合预期等,这些在程序运行期间发生的非正常状况,就是「异常(Exception)」。
异常并非等同于“程序报错导致直接崩溃”,Java 提供了完善的异常处理机制,允许我们捕获、处理这些意外状况,甚至自定义业务相关的异常,从而保证程序的健壮性和稳定性,让程序在遇到意外时也能优雅地继续运行或友好地提示问题。
异常的核心分类
Java 中的异常体系以 Throwable 为顶层父类,所有异常和错误都继承自这个类,它主要分为两大分支:
错误(Error)
Error 是程序无法处理的严重问题,通常是由 JVM (Java 虚拟机)抛出的,与程序本身的业务逻辑无关。这类问题发生时,JVM 往往会终止运行,我们无法通过代码捕获和处理。
常见示例:
OutOfMemoryError:内存溢出错误,程序占用内存超过 JVM 分配的上限StackOverflowError:栈溢出错误,通常由无限递归调用导致
异常(Exception)
Exception 是程序可以处理的轻微问题,也是我们日常开发中重点关注的对象。它又可以分为两类:
-
受查异常(编译时异常)
- 定义:继承自
Exception类(不包含RuntimeException及其子类),在程序编译阶段就会被编译器强制检查。 - 特点:如果一个方法可能抛出受查异常,必须用
throws关键字声明;调用该方法的代码,必须用try-catch捕获异常,或继续用throws向上抛出,否则编译无法通过。 - 常见示例:
IOException(文件输入输出异常)、ClassNotFoundException(类找不到异常)
- 定义:继承自
-
非受查异常(运行时异常)
- 定义:继承自
RuntimeException类,在程序运行阶段才会出现,编译阶段编译器不会强制检查。 - 特点:无需强制声明
throws,也无需强制捕获,即使不做任何处理,编译也能通过;如果运行时发生该异常,默认会由 JVM 打印异常堆栈信息并终止程序。 - 常见示例:
NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组下标越界异常)、IllegalArgumentException(非法参数异常)
- 定义:继承自
异常的核心处理关键字
Java 提供了 5 个核心关键字来处理异常,构成了异常处理的基础框架:
try:用于包裹可能抛出异常的代码块,监控代码的运行状况。catch:紧跟在try之后,用于捕获try代码块中抛出的异常,并进行针对性处理(一个try可以对应多个catch,捕获不同类型的异常)。finally:可选,紧跟在catch之后,无论try代码块是否抛出异常、catch是否捕获到异常,finally中的代码都会执行,通常用于释放资源(如关闭文件流、数据库连接)。throw:用于在方法内部主动抛出一个具体的异常对象(可以是内置异常,也可以是自定义异常)。throws:用于在方法声明处声明该方法可能抛出的异常类型,告知调用者该方法存在异常风险,需要进行处理(主要用于受查异常)。
二、自定义异常(登录场景示例)
Java 提供的内置异常(如 NullPointerException、IOException)只能描述通用的异常场景,无法满足我们实际开发中的各类业务场景需求。
比如登录功能中,“用户名错误”和“密码错误”都属于业务异常,用内置异常无法清晰区分二者的差异,也无法返回针对性的业务提示信息。此时,我们就可以通过自定义异常类来描述这些特定业务的异常信息,让异常处理更清晰、更贴合业务需求。
核心说明
- 自定义受查异常(编译时异常):需要继承
Exception类,方法抛出该异常时必须用throws声明,调用者必须捕获或继续抛出。 - 自定义非受查异常(运行时异常):需要继承
RuntimeException类,无需强制声明和捕获,使用更灵活。 - 自定义异常类通常需要提供两个构造方法:无参构造、带异常信息的构造方法(通过
super()调用父类对应构造方法传递异常信息)。 - 业务方法中通过
throw关键字主动抛出异常对象,通过try-catch关键字捕获并处理异常,实现业务逻辑的异常分流。
用户名异常类(UserNameException)
自定义受查异常,专门用于描述「用户名错误」相关的业务异常,清晰区分登录场景中的用户名问题。
// 需继承 Exception 类(表示这是一个受查异常,编译时会强制检查)
public class UserNameException extends Exception{
// 1. 无参构造方法:提供默认异常描述
public UserNameException() {
}
// 2. 带异常信息的构造方法(将自定义业务异常信息传递给父类 Exception)
public UserNameException(String message) {
super(message);
}
}
密码异常类(PassWordException)
自定义受查异常,专门用于描述「密码错误」相关的业务异常,与用户名异常做明确区分。
// 需继承 Exception 类(表示这是一个受查异常,编译时会强制检查)
public class PassWordException extends Exception{
// 1. 无参构造方法:提供默认异常描述
public PassWordException() {
}
// 2. 带异常信息的构造方法(将自定义业务异常信息传递给父类 Exception)
// 注意:修复原代码中的构造方法名错误(原错误为 UserNameException)
public PassWordException(String message) {
super(message);
}
}
登录业务类(Login)
包含登录核心业务逻辑,主动判断用户名和密码的正确性,不正确则抛出对应的自定义异常,同时在 main 方法中通过 try-catch 捕获并处理不同的业务异常,实现优雅的异常分流。
public class Login {
// 预设的正确用户名和密码(模拟数据库中的合法用户信息)
public String userName = "LiHua";
public String passWord = "hehe";
// 受查异常需要用 throws 声明(告知调用者该方法可能抛出这两个业务异常)
public void loginInfo(String userName, String passWord) throws UserNameException, PassWordException {
// 1. 判断用户名是否正确,不正确则主动抛出 UserNameException
if (!userName.equals(this.userName)) {
throw new UserNameException("用户名错误:不存在该用户,请检查用户名是否输入正确");
}
// 2. 判断密码是否正确,不正确则主动抛出 PassWordException
if (!passWord.equals(this.passWord)) {
throw new PassWordException("密码错误:密码与用户名不匹配,请重新输入密码");
}
// 3. 用户名和密码都正确,提示登录成功
System.out.println("登录成功:欢迎 " + this.userName + " 进入系统!");
}
public static void main(String[] args) {
Login login = new Login();
// 尝试调用登录方法,捕获可能抛出的自定义业务异常
try {
// 传入错误密码,会触发 PassWordException(可修改参数测试用户名异常)
login.loginInfo("LiHua", "heihei");
} catch (UserNameException e1) {
// 捕获用户名异常并进行业务处理
System.out.println("【异常捕获】用户名相关异常:");
e1.printStackTrace(); // 打印异常堆栈信息,便于开发人员排查问题
} catch (PassWordException e2) {
// 捕获密码异常并进行业务处理
System.out.println("【异常捕获】密码相关异常:");
e2.printStackTrace(); // 打印异常堆栈信息,便于开发人员排查问题
}
}
}
补充:运行结果说明
当传入用户名正确、密码错误(login.loginInfo("LiHua", "heihei"))时,程序运行结果如下:
【异常捕获】密码相关异常:
PassWordException: 密码错误:密码与用户名不匹配,请重新输入密码
at Login.loginInfo(Login.java:16)
at Login.main(Login.java:29)
从结果可以看到,程序没有直接崩溃,而是精准捕获了「密码错误」的业务异常,并且返回了针对性的提示信息,便于用户理解问题,也便于开发人员排查问题,这就是自定义业务异常的核心价值。
总结
- Java 异常体系顶层为
Throwable,分为无法处理的Error和可处理的Exception,Exception又分为受查异常(编译时强制检查)和非受查异常(运行时触发)。 - 异常处理的核心关键字有
try、catch、finally、throw、throws,共同构成异常处理框架。 - 自定义异常需继承
Exception(受查)或RuntimeException(非受查),通常提供无参和带消息的构造方法,核心价值是贴合业务场景、清晰区分异常类型。