抛出和捕获异常
导航 异常 主题: ) |
语言编译器擅长指出程序中大部分错误代码,但是有一些错误只有在程序执行时才会显现。考虑 代码清单 6.1;这里,程序定义了一个方法 divide,它执行一个简单的除法运算,以两个整数作为参数参数并返回它们的除法结果。可以安全地假设,当调用 divide(4, 2) 语句时,它将返回数字 2。但是,考虑下一条语句,该语句依赖于提供的命令行参数来生成除法运算。如果用户提供数字零(0)作为第二个参数怎么办?我们都知道零除是不可能的,但编译器不可能预料到用户会提供零作为参数。
|
|
这种在程序运行时导致错误解释的异常代码通常会导致在 Java 中称为异常的错误。当 Java 解释器遇到异常代码时,它会停止执行并显示有关发生的错误的信息。此信息称为堆栈跟踪。上面的例子中的 堆栈跟踪 告诉我们更多关于错误的信息,例如发生异常的线程——"main"
——异常类型——java.lang.ArithmeticException
,一个易于理解的显示消息——/ by zero
,以及可能发生异常的确切方法和行号。
异常对象
[edit | edit source]前面的异常可以由开发人员显式创建,就像以下代码一样
|
|
注意,当b
等于零时,没有返回值。它不是由 Java 解释器本身生成的 java.lang.ArithmeticException
,而是由编码器创建的异常。结果是一样的。它向您展示了一个异常是一个对象。它的主要特点是可以抛出。异常对象必须继承自 java.lang.Exception
。标准异常有两个构造函数
- 默认构造函数;以及,
- 一个带字符串参数的构造函数,这样你就可以在异常中放置相关信息。
代码部分 6.1: 使用默认构造函数的异常对象的实例。
new Exception();
|
代码部分 6.2: 通过在构造函数中传递字符串来实例化 Exception 对象。
new Exception("Something unexpected happened");
|
这个字符串可以稍后使用各种方法提取,如 代码清单 6.2 所示。
你可以使用关键字 throw
抛出任何类型的 Throwable 对象。它会中断方法。throw 语句之后的任何内容都不会被执行,除非抛出的异常被 处理。异常对象不会从方法中返回,而是从方法中抛出。这意味着异常对象不是方法的返回值,调用方法也可以被中断,依此类推……
通常,您会为每种不同类型的错误抛出不同类型的异常。有关错误的信息既表示在异常对象内部,也隐含在异常类名称中,因此在更大的上下文中的人可以弄清楚如何处理您的异常。通常,唯一的信息是异常类型,并且没有有意义的信息存储在异常对象中。
Oracle 标准异常类
[edit | edit source]下面的框 6.1 介绍了 java.lang
包中的各种异常类。
框 6.1: Java 异常类
|
图 6.2: JCL 中的异常类及其继承模型。 |
try
/catch
语句
[edit | edit source]Try/Catch 绝对更好。默认情况下,当抛出异常时,当前方法会中断,调用方法也会中断,依此类推,直到main
方法。抛出的异常也可以使用try
/catch
语句捕获。下面是如何使用try
/catch
语句:
代码部分 6.3: 将除法运算放入try 块中。
int a = 4;
int b = 2;
int result = 0;
try {
int c = a / b;
result = c;
} catch(ArithmeticException ex) {
result = 0;
}
return result;
|
已执行的代码行已突出显示。当没有抛出异常时,方法流程执行try
语句,而不是catch
语句。
代码部分 6.4:捕获“除以零”错误。
int a = 4;
int b = 0;
int result = 0;
try {
int c = a / b;
result = c;
} catch(ArithmeticException ex) {
result = 0;
}
return result;
|
由于在第 5 行抛出了异常,第 6 行未执行,但异常被catch
语句捕获,因此执行catch
块。以下代码也被执行。请注意,catch
语句将异常作为参数。还有第三种情况:当异常不属于与参数相同的类时
代码部分 6.5:未捕获异常。
int a = 4;
int b = 0;
int result = 0;
try {
int c = a / b;
result = c;
} catch(NullPointerException ex) {
result = 0;
}
return result;
|
就好像没有try
/catch
语句一样。异常被抛出到调用方法。
try
/catch
语句可以包含多个catch
块,以不同的方式处理不同的异常。每个catch
块必须采用一个不同可抛出类的参数。抛出的对象可能匹配多个catch
块,但只有第一个与对象匹配的catch
块将被执行。当且仅当以下条件满足时,catch 块将捕获抛出的异常
- 抛出的异常对象与 catch 块指定的异常对象相同。
- 抛出的异常对象是 catch 块指定的异常对象的子类型。
这意味着catch
块的顺序很重要。因此,不能将捕获所有异常的catch
块(以java.lang.Exception
作为参数)放在捕获更具体异常的catch
块之前,因为第二个块永远不会被执行。
|
|
在第 14 行,我们使用了一个 **多捕获** 子句。它从 JDK 7 开始可用。这是多个 **catch** 子句的组合,它允许你在单个处理程序中处理异常,同时保留它们的类型。因此,它们不会被封装在一个父异常超类中,而是保留它们各自的类型。
你也可以在这里使用java.lang.Throwable
类,因为 **Throwable** 是 *应用程序特定* **Exception** 类的父类。但是,在 Java 编程圈中不建议这样做。这是因为 **Throwable** 恰好也是 *非应用程序特定* **Error** 类的父类,而这些类不打算被显式处理,因为它们由 JVM 本身处理。
可以在catch
块之后添加一个finally
块。finally
块始终被执行,即使没有抛出异常、异常被抛出并被捕获,或者异常被抛出但未被捕获。它是一个放置应始终在不安全操作(如文件关闭或数据库断开连接)之后执行的代码的地方。你可以定义一个没有catch
块的try
块,但是在这种情况下,它必须后跟一个finally
块。
让我们检查以下代码
在 代码部分 6.7 中,methodC
是无效的。因为methodA
和 methodB
传递(或抛出)异常,所以methodC
必须准备好处理它们。这可以通过两种方式处理:一个try
-catch
块,它将在方法内部处理异常,以及一个throws
子句,它将反过来将异常抛出给调用者以进行处理。上面的示例将导致编译错误,因为 Java 对异常处理非常严格。因此,程序员被迫在某个时刻处理任何可能的错误情况。
方法可以对异常执行两件事:通过throws
声明要求调用方法处理它,或者通过try
-catch
块在方法内部处理异常。
要正常工作,原始代码可以以多种方式修改。例如,以下
代码部分 6.8:捕获和抛出异常。
public void methodC() throws CustomException, SomeException {
try {
methodB();
} catch(AnotherException e) {
// Handle caught exceptions.
}
methodA();
}
|
来自methodB
的AnotherException
将在本地处理,而CustomException
和 SomeException
将被抛出给调用者以进行处理。当开发人员必须在这两个选项之间做出选择时,大多数开发人员会感到尴尬。这种类型的决定不应在开发时做出。如果你是开发团队,应该在所有开发人员之间讨论,以制定共同的异常处理策略。