文章目录
一、异常概述
1.1 什么是异常
异常(exception)是在程序执行期间发生的事件,它会破坏程序指令的正常流程。
exception 是 exceptional event 的缩写
当方法中发生错误时,该方法创建一个对象并将其交给运行时系统。该对象称为异常对象(exception object),包含有关错误的信息,包括其类型和错误发生时程序的状态。创建异常对象并将其交给运行时系统称为抛出异常(throw exception)。
1.2 引入异常的好处
引入异常处理机制为程序设计提供了许多好处,以下是主要的一些好处:
- 将错误处理代码与常规代码分开
- 在调用堆栈中传播错误
- 分组并区分错误类型
1.3 异常处理流程
在一个方法抛出异常后,运行时系统试图在调用堆栈中搜索包含可以处理异常的代码块的方法。该代码块称为异常处理程序(exception handler)。
搜索从发生错误的方法开始,并以调用方法的相反顺序继续通过调用堆栈。当找到合适的处理程序时,运行时系统将异常传递给处理程序。如果抛出的异常对象的类型与处理程序可以处理的类型匹配,则认为异常处理程序是合适的。
简单来说,异常处理的逻辑遵循调用链,即从方法的调用者向上传递。
1.4 异常处理机制的要求
异常通常分为已检查异常(Checked Exceptions)、未检查异常(Unchecked Exceptions)、错误(Errors)。其中,抛出已检查异常的代码必须满足以下两种情况之一:
-
捕获异常(Catch the Exception):使用
try
语句捕获异常。try
语句必须提供异常处理程序,即包含catch
块,用于处理可能抛出的异常。try { // 可能抛出异常的代码 } catch (SomeException e) { // 异常处理代码 }
-
指定异常(Specify the Exception):方法声明中指定它可能抛出异常。方法必须提供
throws
子句,列出可能抛出的异常。public void someMethod() throws SomeException { // 可能抛出异常的代码 }
如果代码不满足上述要求,将无法编译通过。
二、异常类型
2.1 异常类别
异常分为三大类:
- 已检查异常(Checked Exceptions):
- 这些异常是编译时异常,必须要么被捕获要么被声明。编译器在在编译阶段会进行检查。
- 这些是编写良好的应用程序应该预见并从中恢复的异常条件。
- 例如:
FileNotFoundException
、IOException
,SQLException
。
- 未检查异常(Unchecked Exceptions):
- 这些异常是运行时异常,继承自
RuntimeException
。未检查异常存在争议 - 这些异常是应用程序内部的异常情况,应用程序通常无法预测或恢复。这些通常表明编程错误,如逻辑错误或 API 使用不当。
- 例如:
NullPointerException
,ArrayIndexOutOfBoundsException
。
- 这些异常是运行时异常,继承自
- 错误(Errors):
- 这些是由 JVM 引发的严重错误,继承自
Error
。 - 这些是应用程序外部的异常情况,应用程序通常无法预测或从中恢复。
- 例如:
OutOfMemoryError
,StackOverflowError
。
- 这些是由 JVM 引发的严重错误,继承自
其中,未检查异常(Unchecked Exceptions) 和错误(Errors)不需要遵循捕获或指定异常的要求。也就是说,针对这两种异常,我们可以不用编写异常处理程序进行处理。
注意:编译时异常是指编译器在编译阶段进行检查的异常类型,但这些异常实际上是在程序运行时可能会发生的。例如,FileNotFoundException
是编译时异常,如果指定的文件在运行时不存在,则会抛出这个异常。
2.2 Exception 类的层次
Throwable
类的直接后代有:
三、抛出异常
3.1 throws 关键字
throws
关键字是 Java 中用于方法声明的一部分,它指明了该方法可能会抛出某些类型的已检查异常。可以这样理解,方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
public void readFile(String fileName) throws FileNotFoundException {
// 方法实现
}
3.2 throw 关键字
throw
关键字可以在代码中抛出一个异常对象。当一个方法使用 throw
抛出异常时,程序控制权会转移到该方法的调用者。调用者需要有适当的 try-catch
结构来捕获并处理该异常。
throw 语句需要一个参数:一个可抛出对象。可抛出对象是 Throwable 类的任何子类的实例。
throw someThrowableObject;
示例如下:
public void validate(int value) {
if (value < 0) {
throw new IllegalArgumentException("Negative value not allowed.");
}
}
3.3 链式异常
应用程序通常通过抛出另一个异常来响应已发生的异常。换句话说,第一个异常可能会导致第二个异常的产生。了解何时一个异常会导致另一个异常是非常重要的,这种机制被称为链式异常(Chained Exception),它有助于程序员追踪异常的源头和原因。
Throwable
常见的支持链式异常的方法如下:
Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)
示例如下:
try {
} catch (IOException e) {
throw new SampleException("Other IOException", e);
}
这段代码展示了如何在捕获 IOException
异常后,创建一个新的自定义异常 SampleException
,并附加原始原因。然后,将异常链抛出到下一个更高级别的异常处理程序。
3.4 throw 和 throws 的区别
当你在方法中使用 throw
抛出异常时,这表示在特定条件下发生了某种错误。使用 throws
声明异常时,你是在声明这个方法可能会引发错误,以便调用这个方法的代码能做好相应的异常处理。
throw 和 throws 的区别在于:
特性 | throw | throws |
---|---|---|
作用 | 抛出一个异常实例 | 声明方法可能抛出的异常类型 |
使用位置 | 方法内部 | 方法签名(声明部分) |
语法格式 | throw new ExceptionType() | void methodName() throws ExceptionType |
强制要求 | 不强制要求捕获 | 调用方法时必须捕获或继续声明异常 |
四、捕获异常(异常处理程序)
在抛出异常后,运行时系统会在调用堆栈中找到异常处理程序来处理该异常。
另外,异常处理程序里面可以在 catch 块里面再次抛出异常与链式异常。通常,希望改变异常的类型时才会这样做。
4.1 try-catch-finally
异常处理程序主要是由异常处理程序组件 try、catch 、finally 来实现。其中:
- try:用于捕获异常。try 块不能单独存在,后面至少跟着一个 catch块 或者 一个 finally 块,也可以跟着多个 catch 块。
- catch:处理异常,被称为异常处理程序。另外,单个catch 块可以处理多种类型的异常,不同类型的异常用
|
分割 - finally:用于在异常处理结束后执行清理工作,无论是否抛出异常。当在
try
块或catch
块中遇到return
语句时,finally
语句块将在方法返回之前被执行。注意,当 JVM 退出时,finally 块可能不会执行
try {
// 初始化资源或执行可能抛出异常的操作
resource = initializeResource()
// 执行主要操作
performMainTask(resource)
} catch (SpecificExceptionType1 exception1) {
// 处理特定类型的异常1
handleExceptionType1(exception1)
} catch (SpecificExceptionType2 exception2) {
// 处理特定类型的异常2
handleExceptionType2(exception2)
} catch (GeneralException exception) {
// 处理其他一般性异常
handleGeneralException(exception)
} finally {
// 清理资源或执行无论如何都会执行的操作
if (resource is not null) {
resource.close()
}
}
4.2 try-with-resources
try-with-resource 语法结构,旨在自动管理资源,确保资源在使用后能够及时关闭,避免资源泄露 。无论代码块中的操作是否成功,资源都会在 try 代码块执行完毕后自动关闭。。
try-with-resources 语句特别适合使用 Closeable
资源的情况,例如流。
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// 读取文件的代码
} catch (IOException e) {
// 处理异常
}