一、异常的基本概念
(一)异常的定义与分类
- 核心定义:异常是Java程序运行时出现的非正常情况(如除0、数组索引越界),会中断程序正常流程,Java通过异常类封装这些情况并提供处理机制。
- 继承体系:所有异常的父类是
Throwable,其下分为两类:
Error类:系统级错误(如资源耗尽),程序无法恢复,无需处理。
Exception类:程序级异常,可通过代码处理,是异常处理的核心。
- 编程案例(认识异常):
public class ExceptionDemo {
// 两数相除方法
public static int divide(int x, int y) {
return x / y; // 可能出现除0异常
}
public static void main(String[] args) {
int result = divide(4, 0); // 除数为0,触发异常
System.out.println(result); // 异常后代码无法执行
}
}
- 案例注意事项:
- 上述代码运行时会抛出
ArithmeticException(算术异常),程序直接中断,后续打印语句无法执行。
- 异常未处理时,JVM会自动打印异常信息(类名、原因、堆栈轨迹)并终止程序。
- 逻辑思维培养:
- 养成"预判异常"思维:编写代码时思考可能出现的非正常情况(如参数为0、数组为空),提前规划处理方案。
- 理解"异常分层":区分
Error和Exception,聚焦Exception的处理,避免无效编码。
二、运行时异常与编译时异常
(一)两类异常的定义与区别
- 运行时异常(unchecked异常):
- 定义:
RuntimeException类及其子类(如NullPointerException、IndexOutOfBoundsException),编译时不强制处理,运行时触发。
- 特点:多由逻辑错误导致(如数组索引越界),可通过优化代码避免。
- 编译时异常(checked异常):
- 定义:
Exception类中非RuntimeException的子类(如IOException),编译时必须处理(捕获或声明抛出),否则无法通过编译。
- 编程案例(运行时异常):
public class RuntimeExceptionDemo {
public static void main(String[] args) {
int[] arr = new int[5];
System.out.println(arr[6]); // 数组索引越界异常(运行时异常)
}
}
- 案例注意事项:
- 上述代码编译时无报错,但运行时抛出
ArrayIndexOutOfBoundsException,属于运行时异常。
- 编译时异常(如文件操作相关的
IOException)必须显式处理,否则编译器直接报错。
- 逻辑思维培养:
- 区分"可避免异常"与"必须处理异常":运行时异常通过规范逻辑(如先判断索引范围)避免,编译时异常需提前规划处理流程。
- 养成"先校验后执行"习惯:访问数组前判断索引范围,调用方法前校验参数合法性,减少运行时异常。
三、异常处理语法
(一)try...catch语句
- 核心作用:捕获并处理异常,使程序在异常后仍能继续执行。
- 语法格式:
try {
// 可能触发异常的代码
} catch (异常类型 e) {
// 异常处理逻辑
}
- 编程案例:
public class TryCatchDemo {
public static int divide(int x, int y) {
return x / y;
}
public static void main(String[] args) {
try {
int result = divide(4, 0); // 可能触发异常的代码
System.out.println(result); // 异常后此句不执行
} catch (ArithmeticException e) {
// 处理异常:打印异常信息
System.out.println("异常原因:" + e.getMessage());
}
System.out.println("程序继续执行..."); // 异常处理后正常执行
}
}
- 案例注意事项:
try块中异常发生后,后续代码立即终止,直接进入匹配的catch块。
- 多个
catch块捕获不同异常时,子类异常需放在父类异常前面(如ArithmeticException在前,Exception在后)。
- 逻辑思维培养:
- 养成"精准捕获"思维:明确
try块可能触发的异常类型,避免直接捕获Exception(不利于定位问题)。
- 设计"异常处理逻辑":根据场景选择处理方式(如打印日志、返回默认值、提示用户),而非仅打印异常信息。
(二)finally语句
- 核心作用:无论是否发生异常,
finally块中的代码都会执行,常用于释放资源(如关闭文件、数据库连接)。
- 语法格式:
try {
// 可能触发异常的代码
} catch (异常类型 e) {
// 异常处理逻辑
} finally {
// 必须执行的代码(如资源释放)
}
- 编程案例:
public class FinallyDemo {
public static int divide(int x, int y) {
return x / y;
}
public static void main(String[] args) {
try {
int result = divide(4, 0);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("异常原因:" + e.getMessage());
return; // 提前结束方法
} finally {
System.out.println("finally块执行:释放资源"); // 仍会执行
}
}
}
- 案例注意事项:
- 即使
catch块中有return、break等语句,finally块依然执行(除非调用System.exit(0)终止JVM)。
finally块中避免使用return,否则会覆盖try或catch块的返回值。
- 逻辑思维培养:
- 建立"资源闭环"思维:打开的资源(文件、网络连接)必须在
finally中释放,避免资源泄露。
- 区分"必须执行"与"可选执行":将清理工作放入
finally,核心业务逻辑放入try,异常处理放入catch,结构清晰。
四、抛出异常
(一)throws关键字
- 核心作用:声明方法可能抛出的异常,将异常处理责任交给调用者(调用者需捕获或继续声明)。
- 语法格式:
修饰符 返回值类型 方法名(参数) throws 异常类1, 异常类2... { 方法体 }
- 编程案例:
public class ThrowsDemo {
// 声明方法可能抛出ArithmeticException
public static int divide(int x, int y) throws ArithmeticException {
return x / y;
}
public static void main(String[] args) {
try {
int result = divide(4, 0); // 调用声明异常的方法,必须处理
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("处理异常:" + e.getMessage());
}
}
}
- 案例注意事项:
- 方法声明的异常需是实际可能抛出的异常,避免冗余声明。
- 调用声明编译时异常的方法,必须通过
try...catch捕获或throws继续声明,否则编译报错。
- 逻辑思维培养:
- 养成"责任传递"思维:方法内部无法处理的异常,通过
throws交给调用者,明确异常处理边界。
- 避免"盲目声明":仅声明方法确实可能抛出的异常,减少调用者的处理负担。
(二)throw关键字
- 核心作用:在方法体内主动抛出异常实例,常用于触发自定义业务异常(如参数非法)。
- 语法格式:
throw new 异常类(异常信息);
- 编程案例:
public class ThrowDemo {
// 校验年龄合法性,非法则主动抛出异常
public static void printAge(int age) throws Exception {
if (age <= 0 || age > 120) {
// 主动抛出异常,携带自定义信息
throw new Exception("年龄非法:" + age + ",必须在1-120之间");
}
System.out.println("年龄:" + age);
}
public static void main(String[] args) {
try {
printAge(-5); // 触发主动抛出的异常
} catch (Exception e) {
System.out.println("捕获异常:" + e.getMessage());
}
}
}
- 案例注意事项:
throw抛出编译时异常时,方法需通过throws声明该异常;抛出运行时异常则无需声明。
throw语句后需立即终止代码(如return),否则后续代码无法执行。
- 逻辑思维培养:
- 建立"主动校验"思维:业务逻辑中主动校验参数、状态合法性,通过
throw提前触发异常,避免后续错误。
- 设计"异常信息":抛出异常时携带明确信息(如"年龄非法:-5"),便于定位问题。
五、自定义异常
(一)自定义异常的定义与使用
- 核心定义:Java提供的异常类无法满足业务需求时,自定义异常类(需继承
Exception或其子类)。
- 实现步骤:
- 继承
Exception类(编译时异常)或RuntimeException类(运行时异常)。
- 提供无参和带异常信息的构造方法,调用父类构造。
- 编程案例:
// 自定义异常类:除数为负数异常(编译时异常)
class DivideByMinusException extends Exception {
// 无参构造
public DivideByMinusException() {
super();
}
// 带异常信息的构造
public DivideByMinusException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
// 两数相除,除数为负数则抛出自定义异常
public static int divide(int x, int y) throws DivideByMinusException {
if (y < 0) {
throw new DivideByMinusException("除数为负数:" + y);
}
return x / y;
}
public static void main(String[] args) {
try {
int result = divide(4, -2);
System.out.println(result);
} catch (DivideByMinusException e) {
System.out.println("处理自定义异常:" + e.getMessage());
}
}
}
- 案例注意事项:
- 自定义编译时异常(继承
Exception)需显式处理(捕获或声明);自定义运行时异常(继承RuntimeException)则无需。
- 自定义异常类名需见名知义(如
DivideByMinusException),便于理解异常含义。
- 逻辑思维培养:
- 养成"业务异常抽象"思维:将特定业务场景的异常(如"余额不足""参数非法")抽象为自定义异常,使代码更具可读性。
- 区分"通用异常"与"业务异常":通用异常(如除0)使用Java自带类,业务特有异常(如除数为负)使用自定义类,边界清晰。