一、异常的基本概念
(一)异常的定义与分类
核心定义:异常是Java程序运行时出现的非正常情况(如除0、数组索引越界),会中断程序正常流程,Java通过异常类封装这些情况并提供处理机制。
继承体系:所有异常的父类是Throwable,其下分为两类:
Error类:系统级错误(如资源耗尽),程序无法恢复,无需处理。
Exception类:程序级异常,可通过代码处理,是异常处理的核心。
编程案例(认识异常):
java
复制代码
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),编译时必须处理(捕获或声明抛出),否则无法通过编译。
编程案例(运行时异常):
java
复制代码
public class RuntimeExceptionDemo {
public static void main(String[] args) {
int[] arr = new int[5];
System.out.println(arr[6]); // 数组索引越界异常(运行时异常)
}
}
案例注意事项:
上述代码编译时无报错,但运行时抛出ArrayIndexOutOfBoundsException,属于运行时异常。
编译时异常(如文件操作相关的IOException)必须显式处理,否则编译器直接报错。
逻辑思维培养:
区分"可避免异常"与"必须处理异常":运行时异常通过规范逻辑(如先判断索引范围)避免,编译时异常需提前规划处理流程。
养成"先校验后执行"习惯:访问数组前判断索引范围,调用方法前校验参数合法性,减少运行时异常。
三、异常处理语法
(一)try...catch语句
核心作用:捕获并处理异常,使程序在异常后仍能继续执行。
语法格式:
java
复制代码
try {
// 可能触发异常的代码
} catch (异常类型 e) {
// 异常处理逻辑
}
编程案例:
java
复制代码
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块中的代码都会执行,常用于释放资源(如关闭文件、数据库连接)。
语法格式:
java
复制代码
try {
// 可能触发异常的代码
} catch (异常类型 e) {
// 异常处理逻辑
} finally {
// 必须执行的代码(如资源释放)
}
编程案例:
java
复制代码
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... { 方法体 }
编程案例:
java
复制代码
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 异常类(异常信息);
编程案例:
java
复制代码
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类(运行时异常)。
提供无参和带异常信息的构造方法,调用父类构造。
编程案例:
java
复制代码
// 自定义异常类:除数为负数异常(编译时异常)
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自带类,业务特有异常(如除数为负)使用自定义类,边界清晰。