原文: https://www.programiz.com/java-programming/exception-handling
在上一教程中,我们了解了异常。 异常是程序执行期间发生的意外事件。
在 Java 中,我们使用异常处理器组件try
,catch
和finally
块来处理异常。
为了捕获和处理异常,我们将try...catch...finally
块放置在可能生成异常的代码周围。finally
块是可选的。
try...catch...finally
的语法为:
try {
// code
} catch (ExceptionType e) {
// catch block
} finally {
// finally block
}
可能产生异常的代码位于try
块中。
每个try
块后应紧跟catch
或finally
块。 发生异常时,它会被紧随其后的catch
块捕获。
catch
块不能单独使用,并且必须始终在try
块之前。
class Main {
public static void main(String[] args) {
try {
int divideByZero = 5 / 0;
System.out.println("Rest of code in try block");
} catch (ArithmeticException e) {
System.out.println("ArithmeticException => " + e.getMessage());
}
}
}
输出
ArithmeticException => / by zero
在这个例子中
- 我们在
try
块中将数字除以 0。 这产生了ArithmeticException
。 - 发生异常时,程序将跳过
try
块中的其余代码。 - 在这里,我们创建了一个
catch
块来处理ArithmeticException
。 因此,将执行catch
块中的语句。
如果try
块中的所有语句均未生成异常,则将跳过catch
块。
对于每个try
块,可以有零个或多个catch
块。
每个catch
块的参数类型指示可以处理的异常类型。 多个catch
块使我们能够以不同方式处理每个异常。
class ListOfNumbers {
public int[] arrayOfNumbers = new int[10];
public void writeList() {
try {
arrayOfNumbers[10] = 11;
} catch (NumberFormatException e1) {
System.out.println("NumberFormatException => " + e1.getMessage());
} catch (IndexOutOfBoundsException e2) {
System.out.println("IndexOutOfBoundsException => " + e2.getMessage());
}
}
}
class Main {
public static void main(String[] args) {
ListOfNumbers list = new ListOfNumbers();
list.writeList();
}
}
输出:
IndexOutOfBoundsException => Index 10 out of bounds for length 10
在此示例中,我们声明了大小为 10 的整数arrayOfNumbers
数组。
我们知道数组索引始终从 0 开始。因此,当我们尝试为索引 10 分配值时,会出现IndexOutOfBoundsException
,因为arrayOfNumbers
的数组范围是 0 到 9。
当try
块中发生异常时,
- 异常被引发到第一个
catch
块。 第一个catch
块不处理IndexOutOfBoundsException
,因此将其传递到下一个catch
块。 - 上面示例中的第二个
catch
块是适当的异常处理器,因为它处理IndexOutOfBoundsException
。 因此,它被执行。
对于每个try
块,只能有一个finally
块。
finally
块是可选的。 但是,如果已定义,它将始终执行(即使不会发生异常)。
如果发生异常,则在try...catch
块之后执行该异常。 如果没有异常发生,则在try
块之后执行。
finally
块的基本语法为:
try {
//code
} catch (ExceptionType1 e1) {
// catch block
} catch (ExceptionType1 e2) {
// catch block
} finally {
// finally block always executes
}
class Main {
public static void main(String[] args) {
try {
int divideByZero = 5 / 0;
} catch (ArithmeticException e) {
System.out.println("ArithmeticException => " + e.getMessage());
} finally {
System.out.println("Finally block is always executed");
}
}
}
输出:
ArithmeticException => / by zero
Finally block is always executed
在此示例中,我们将数字除以 0。这将引发ArithmeticException
,该ArithmeticException
被catch
块捕获。finally
块始终执行。
拥有finally
块被认为是一个好习惯。 这是因为它包含重要的清除代码,例如:
return
,continue
或break
语句可能意外跳过的代码- 关闭文件或连接
我们已经提到,最后总是执行,通常就是这种情况。 但是,在某些情况下,finally
块不执行:
- 使用
System.exit()
方法 finally
块中发生异常- 线程的死亡
让我们举一个例子,我们尝试使用FileWriter
创建一个新文件,然后使用PrintWriter
向其中写入数据。
import java.io.*;
class ListOfNumbers {
private int[] list = new int[10];
public ListOfNumbers() {
// storing integer values in the list array
for (int i = 0; i < 10; i++) {
list[i] = i;
}
}
}
public void writeList() {
PrintWriter out = null;
try {
System.out.println("Entering try statement");
// creating a new file OutputFile.txt
out = new PrintWriter(new FileWriter("OutputFile.txt"));
// writing values from list array to the new created file
for (int i = 0; i < 10; i++) {
out.println("Value at: " + i + " = " + list[i]);
}
} catch (IndexOutOfBoundsException e1) {
System.out.println("IndexOutOfBoundsException => " + e1.getMessage());
} catch (IOException e2) {
System.out.println("IOException => " + e2.getMessage());
} finally {
// checking if PrintWriter has been opened
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
} else {
System.out.println("PrintWriter not open");
}
}
}
}
class Main {
public static void main(String[] args) {
ListOfNumbers list = new ListOfNumbers();
list.writeList();
}
}
运行此程序时,可能会发生两种情况:
try
块中发生异常try
块正常执行
创建新的FileWriter
时可能会发生异常。 如果指定的文件无法创建或写入,则抛出IOException
。
当发生异常时,我们将获得以下输出。
Entering try statement
IOException => OutputFile.txt
PrintWriter not open
当没有异常发生并且try
块正常执行时,我们将获得以下输出。
Entering try statement
Closing PrintWriter
创建了OutputFile.txt
,它将具有以下内容:
Value at: 0 = 0
Value at: 1 = 1
Value at: 2 = 2
Value at: 3 = 3
Value at: 4 = 4
Value at: 5 = 5
Value at: 6 = 6
Value at: 7 = 7
Value at: 8 = 8
Value at: 9 = 9
让我们尝试在上述示例的帮助下详细了解异常处理的流程。
上图描述了在创建新的FileWriter
时发生异常时的程序执行流程。
- 为了进入发生异常的方法,
main
方法调用writeList()
方法,然后调用FileWriter()
方法来创建新的OutputFile.txt
文件。 - 发生异常时,运行系统将跳过
try
块中的其余代码。 - 它开始以相反的顺序搜索调用栈,以找到合适的异常处理器。
- 这里
FileWriter
没有异常处理器,因此运行时系统检查调用栈中的下一个方法,即writeList
。 writeList
方法有两个异常处理器:一个处理IndexOutOfBoundsException
,另一个处理IOException
。- 然后,系统依次处理这些处理器。
- 此示例中的第一个处理器处理
IndexOutOfBoundsException
。 这与try
块引发的IOException
不匹配。 - 因此,检查下一个处理器是
IOException
处理器。 这与引发的异常类型匹配,因此将执行catch
块中的代码。 - 执行异常处理器后,将执行
finally
块。 - 在这种情况下,由于
FileWriter
中发生异常,因此中的PrintWriter
对象从未被打开,因此不需要关闭。`
现在,让我们假设在运行该程序时未发生异常,并且try
块正常执行。 在这种情况下,将创建并写入一个OutputFile.txt
。
众所周知,无论异常处理如何,都会执行finally
块。 由于没有异常发生,因此PrintWriter
是打开的,需要关闭。 这是通过finally
块中的out.close()
语句完成的。
从 Java SE 7 和更高版本开始,我们现在可以使用一个catch
块捕获不止一种类型的异常。
这样可以减少代码重复并提高代码的简单性和效率。
catch
块可以处理的每种异常类型都使用竖线|
分隔。
其语法为:
try {
// code
} catch (ExceptionType1 | Exceptiontype2 ex) {
// catch block
}
要了解更多信息,请访问 Java 捕获多个异常。
try-with-resources
语句是一种try
语句,具有一个或多个资源声明。
它的语法是:
try (resource declaration) {
// use of the resource
} catch (ExceptionType e1) {
// catch block
}
资源是在程序结束时要关闭的对象。 必须在try
语句中声明和初始化它。
让我们举个例子。
try (PrintWriter out = new PrintWriter(new FileWriter("OutputFile.txt")) {
// use of the resource
}
try-with-resources
语句也称为自动资源管理。 该语句在语句末尾自动关闭所有资源。
要了解更多信息,请访问 Java try-with-resources
语句。