1. 异常分类:
- Checked Exception(受检异常):在编译时就必须要处理的异常,通常是外部原因导致的问题,如文件未找到、数据库连接失败等。程序必须通过
try-catch
语句或者在方法签名中使用throws
关键字处理。例如:IOException
、SQLException
。 - Unchecked Exception(非受检异常):编译器不会强制要求处理,通常是编程错误,如空指针异常、数组越界等。这类异常继承自
RuntimeException
。例如:NullPointerException
、ArrayIndexOutOfBoundsException
。 - Error:表示程序中无法处理的错误,通常是系统级的,如
OutOfMemoryError
。这些错误是由JVM抛出的,程序不应该捕获这些错误。
2. 异常的继承结构
Throwable
:Java中所有异常和错误的父类。
Exception
:所有可捕获的异常的父类。RuntimeException
:运行时异常的父类,非受检异常。- 受检异常(Checked Exception):需要在编译期处理的异常,所以也叫编译时异常。这些异常是
Exception
类的子类。 - 注意:这里任何的异常(本质上是一个对象)都是在运行时产生的
Error
:表示系统级的错误,如内存溢出等。
注意!!
受检异常(Checked Exception)必须在编译期处理
- Java编译器要求程序员在编写代码时,必须显式处理这些受检异常。处理方法有两种:
- 使用
try-catch
语句捕获并处理异常。 - 在方法签名中使用
throws
关键字声明该方法可能抛出某些异常(抛出某些异常到上层,例如调用者)。
- 使用
- 如果代码没有正确处理或声明受检异常,编译器会报错,这就是为什么这些异常被称为“编译时异常”。
编译器的强制检查
- 编译器在编译阶段会检查代码中的方法调用是否处理了受检异常。这种机制是为了强制程序员考虑可能出现的问题,确保程序对外部资源的操作(如文件、数据库、网络等)有适当的异常处理。
- 例如,如果你尝试打开一个不存在的文件,编译器会要求你处理
FileNotFoundException
(受检异常),否则无法通过编译。
如何声明异常
声明异常是指在方法签名中表明该方法可能会抛出特定类型的异常。当方法可能抛出检查型异常时,必须在方法签名中通过 throws
关键字进行声明。
方法签名中的异常声明
-
语法格式:
返回类型 方法名(参数列表) throws 异常类型1, 异常类型2 { // 方法实现 }
public void readFile(String fileName) throws IOException { // 可能抛出 IOException 的文件读取操作 }
多异常声明
如果一个方法可能抛出多个异常类型,可以在方法签名中声明多个异常,使用逗号分隔。
-
示例:
public void processFile(String fileName) throws IOException, SQLException { // 可能抛出 IOException 或 SQLException }
使用 throws
与 throw
的区别
-
throws
:- 用于方法签名中,声明该方法可能抛出哪些异常。
- 检查型异常必须在方法中使用
throws
声明,编译器会强制要求处理。
-
throw
:- 用于方法体内部,明确抛出一个具体的异常实例。
throw
关键字通常与自定义异常结合使用,开发者可以根据具体情况主动抛出异常。// 声明该方法可能抛出 IOException public void readFile(String fileName) throws IOException { if (fileName == null) { // 主动抛出异常 throw new IOException("文件名不能为空"); } // 文件读取操作 }
检查型异常的处理
检查型异常需要调用者在代码中显式处理。处理方式有两种:
1. 使用 try-catch
捕获异常
调用方法时,可以使用 try-catch
块捕获异常并处理。
public void process() {
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
继续声明抛出异常
如果调用者不想处理该异常,可以在其方法签名中继续声明抛出异常。
-
示例:
public void processFile() throws IOException { readFile("test.txt"); }
使用多分支try-catch
时应该注意的事项:
1. 异常捕获顺序
- 异常捕获顺序非常重要。如果有多个
catch
块,每个块用于处理不同的异常类型,应该按照异常的继承层次从子类到父类的顺序排列。如果父类异常在前,编译器会报错,因为父类异常会捕获所有其子类的异常,后面的catch
块将永远不会执行。try { // 可能抛出多种异常的代码 } catch (FileNotFoundException e) { // 捕获并处理FileNotFoundException } catch (IOException e) { // 捕获并处理IOException }
注意:这里
FileNotFoundException
是IOException
的子类,必须先捕获子类异常。
自定义异常
- Java允许程序员自定义异常,继承自
Exception(外部原因继承)
或RuntimeException
,根据是否希望强制处理。// 继承 Exception(检查型异常) public class MyCheckedException extends Exception { public MyCheckedException(String message) { super(message); } } // 继承 RuntimeException(运行时异常) public class MyUncheckedException extends RuntimeException { public MyUncheckedException(String message) { super(message); } }
- 定义构造函数
-
无参构造函数
- 提供一个默认构造函数,允许创建没有详细消息的异常实例。
public MyCustomException() { super(); }
- 提供一个默认构造函数,允许创建没有详细消息的异常实例。
- 带消息的构造函数
- 接受一个
String
类型的错误消息,用于描述异常的具体情况。public MyCustomException(String message) { super(message); }
- 接受一个
-
// MyCustomException.java
public class MyCustomException extends Exception {
// 无参构造函数
public MyCustomException() {
super();
}
// 带消息的构造函数
public MyCustomException(String message) {
super(message);
}
// 带消息和原因的构造函数
public MyCustomException(String message, Throwable cause) {
super(message, cause);
}
// 带原因的构造函数
public MyCustomException(Throwable cause) {
super(cause);
}
}
- 处理异常: 调用该方法的代码必须显式处理(使用
try-catch
)或继续声明抛出该异常。
处理异常语句try-catch 语句:用于捕获和处理异常。
try {
// 可能抛出异常的代码
} catch (ExceptionType e) {
// 异常处理代码
}
finally
语句:无论是否发生异常,finally
块中的代码都会执行,通常用于关闭资源
try {
// 可能抛出异常的代码
} catch (ExceptionType e) {
// 异常处理代码
} finally {
// 无论是否发生异常,都会执行的代码
}
异常的传播
- 当一个方法抛出异常且没有在本方法中处理时(没有进行捕获,而是throw),该异常会沿着方法调用栈向上传播,直到找到一个捕获该异常的
catch
块。如果没有捕获,程序将会终止。
多重异常处理
- 可以使用多个
catch
块来捕获不同类型的异常try { // 可能抛出多种异常的代码 } catch (IOException e) { // 处理IOException } catch (SQLException e) { // 处理SQLException }
多重异常捕获(Java 7+)
- Java 7之后,可以使用单个
catch
块捕获多个异常,用竖线(|
)分隔try { // 可能抛出多种异常的代码 } catch (IOException | SQLException e) { // 处理IOException和SQLException }
异常处理中常用的方法
获取异常的简单描述信息:
exception.getMessage();
getMessage()
是一个方法,用来获取异常对象中存储的详细错误消息。- 这条消息通常是在创建异常对象时通过构造函数传递进去的,目的是让开发者了解具体的错误原因。
打印异常堆栈信息:
exception.printStackTrace();
printStackTrace()
是一个方法,用于将异常发生时的调用栈信息打印到控制台。堆栈信息可以帮助开发者追踪异常的发生位置以及导致异常的调用链。- 这个方法非常有用,它不仅能告诉你在程序中的哪个地方抛出了异常,还能显示出异常前的调用路径。
public class Main {
public static void main(String[] args) {
try {
// 尝试进行除法操作,分母为 0 会引发异常
int result = divide(10, 0);
System.out.println("结果: " + result);
} catch (ArithmeticException e) {
// 捕获异常并打印异常的描述信息
System.out.println("捕获到异常: " + e.getMessage());
}
}
// 除法方法,可能会引发 ArithmeticException
public static int divide(int numerator, int denominator) {
if (denominator == 0) {
throw new ArithmeticException("除数不能为零!");
}
return numerator / denominator;
}
}
打印堆栈信息:
public class Main {
public static void main(String[] args) {
try {
// 尝试进行除法操作,分母为 0 会引发异常
int result = divide(10, 0);
System.out.println("结果: " + result);
} catch (ArithmeticException e) {
// 捕获异常并打印异常堆栈信息
e.printStackTrace();
}
}
/*
ArithmeticException 通常在程序中执行数学运算时发生,
尤其是在发生非法算术操作时,如除数为零的情况。
*/
public static int divide(int numerator, int denominator) {
if (denominator == 0) {
throw new ArithmeticException("除数不能为零!");
}
return numerator / denominator;
}
}
要会看异常的堆栈信息:
- 堆栈信息的打印是符合栈数据结构的,也就是说,最近的异常信息会在最上面,而较早的调用信息会在堆栈的下面。
- 看异常信息时要注意最开始的描述信息和栈顶信息,因为这些通常能够揭示最接近问题源头的部分。最开始的描述信息通常会说明异常的类型和异常原因,栈顶则指示异常发生的具体代码位置。
注意:
最忌讳的就是捕获异常但是不做处理,此时出现了错误,但是控制台没有报错!!