7.2. 关于异常¶
7.2.1. 目标¶
完成本模块后,学生将能够:
回顾异常和异常处理的目的
回顾并使用 try-catch-finally 块来处理异常
回顾如何在方法中抛出异常
回顾如何实现和测试异常处理代码
7.2.1.1. 建议阅读¶
Java 插话 2 来自 Java 的异常 Data Structures and Abstractions with Java, 4th edition by Frank M. Carrano and Timothy Henry
7.2.2. 交互式:异常处理 [9:31]¶
Follow Along and Engage
在 Eclipse 中,使用 Project > Download Assignment... 菜单命令下载名为"ex07.02-ExceptionHandling"的练习项目。使用此示例跟随以下视频进行学习。请自由尝试。
如需回顾下载 Eclipse 项目的说明,请参见 01.02: Lab: LightBot for Beginners。
下载与视频对应的幻灯片。观看视频时对其做笔记,并亲自练习绘制图表!
7.2.3. 反思 Checked 和 Runtime(Unchecked) 异常¶
7.2.3.1. 已检查异常:¶
这些是在编译时“检查”的异常
已检查异常 必须 要么
捕获(使用 try-catch)或
在抛出它的该方法中声明(使用 throws)——见下方示例
public void myMethod() throws IOException {
// programming statements
}
如果未处理或抛出 Checked Exception,则在尝试编译相关程序时编译器将报告编译错误
Checked 异常示例包括:ClassNotFoundException、FileNotFoundException、IOException 和 NoSuchMethodException。
7.2.3.2. 运行时异常(也称为未检查异常):¶
这些是在编译时不会进行“检查”的异常
与已检查异常不同,任何可能抛出运行时(未检查)异常的代码在编译时都不会出现问题,因此开发人员必须考虑此类异常发生的可能性并相应地准备程序
运行时(未检查)异常示例包括:
ArithmeticException、ArrayIndexOutOfBoundsException、ClassCastException和NullPointerException
Note:
许多学生在各个项目和实验室中都会遇到 NullPointerExceptions。始终记住,这些情况发生在你试图访问一个为 null 的对象时。同样要记住,如果一个对象已声明但从未实例化,或者在其生命周期中的某个时刻变为 null,则该对象可能为 null。排查此类异常的一种简单方法是确定该对象是否确实被实例化。只需检查你认为对象被实例化的代码主体,然后评估并测试以确认该段代码是否按预期执行。
7.2.4. 检查点 1¶
7.2.5. 交互:使用 try、catch 和 finally 进行异常处理 [7:16]¶
要在代码主体内实现异常处理,你必须使用 try-catch-finally 块
您可以使用单个 catch 块,如下所示:
try {
}
catch(ExceptionType eName) {
}
或多个 catch 块。当使用多个 catch 块时 异常处理器的实现顺序很重要。代码应按顺序排列,首先定义最具体的异常处理器,最终进展到最通用异常的定义。
例如:
try{
}
catch(NumberFormatException nfe)
}
catch(Exception e){
}
我们也可以使用 可选 的 finally 块。例如:
try{
}
catch(Exception e){
}
finally{
}
注意,你也可以使用可选的 finally 块与 try 一起,但不使用 catch ,如下所示
try{
}
finally{
}
7.2.6. 稍后处理异常并使用 throws¶
如上所述,有些时候适当实现捕获其内部发生的异常的代码,实质上是在可能引起该异常的代码体内处理该异常。在其他时候,推迟处理给定的异常可能更为合适,将处理该异常的责任推迟或传递给其他调用代码(客户端代码)。当预期或约定调用代码(客户端代码)可能更适合处理该异常时,此选项是适当的。回想一下,调用代码调用了导致异常的该方法,因此可以合理地假设,此类客户端代码可能更适合确定应对异常事件的最佳方式。
为了将异常处理推迟或延迟到客户端代码,我们在可能引发异常(即可能导致异常)的潜在风险方法的声明中添加一个 throws 子句。该 throws 子句必须紧跟在方法名和括号之后,在此子句中我们必须使用 throws 关键字并提供由逗号分隔的该方法的所抛出异常列表(见下方示例)。
public void riskyCodeThatWantsToDefer ( ) throws IOException, NumberFormatException {
// some code
}
throws 子句声明了程序执行期间可能出现的异常,实质上通知调用方/客户端代码需要准备好处理此类情况, 要么通过自身实现 try-catch,要么通过在方法声明中添加 throws 子句将处理委托给其他调用方/客户端代码。
例如,下面的方法调用风险代码,因此它会被通知应该预期会遇到 IOException 或 NumberFormatException,并据此做好准备。
public void callingMethod() {
riskyCodeThatWantsToDefer();
}
编译错误消息 (Unhandled exception type <SomeExceptionName>) 将在客户端代码(callingMethod)未包含捕获或抛出 riskyCodeThatWantsToDefer 声明的异常代码时发生。
7.2.7. 异常处理示例 - 从基础到复杂¶
Try It Yourself
在 Eclipse 中,使用 Project > Download Assignment... 菜单命令下载名为"ex07.02-ExceptionHandling"的练习项目。该包包含一个名为 ExceptionHandlingWithThrow 的项目,您可以使用它来练习 try...catch 结构。请随意尝试。
如需回顾下载 Eclipse 项目的说明,请参见 01.02: Lab: LightBot for Beginners。
7.2.7.1. 交互式:基本 try, catch 示例 [12:05]¶
7.2.7.2. 交互式:通过包含多个 catch 块的 try, catch 示例进行追踪 [14:13]¶
7.2.7.3. 使用"try、catch 和 finally"代码块的示例 [12:33]¶
7.2.8. 实现与测试异常¶
"如果你在你的方法中抛出异常,那么你就应该在测试中捕获它们"
对于本课程,我们将 主要 采用结合 throw 语句使用 try-catch blocks 的异常处理方法。
在实现包含易引发异常代码的方法时,您需要在允许执行风险代码(可能抛出异常的代码) 之前,在方法内部实现检查非常规条件(可能的异常事件)的代码。您必须在测试类中实现 try-catch blocks,以确认失败的代码抛出了正确的异常。在测试类中,您还必须创建使异常得以抛出的必要条件。
注意,这些检查可以通过使用 try-catch 块或条件语句(例如 if 语句)来完成。
您的代码应按如下方式运行:
如果检查通过,则代码执行应按正常流程继续,允许易引发异常的代码执行
如果检查失败,则方法应故意 抛出异常
7.2.8.1. 抛出异常¶
在任何代码中,在适当条件下都有可能抛出异常。您可以使用 throw 语句有意抛出异常。您可以抛出存在的众多异常中的任何一个,或者更具体地说,抛出 Throwable 类的任何子类。
如有必要,您还可以创建自己的自定义异常类,以处理标准 Java 异常类尚未涵盖的意外情况。
要抛出异常,您必须向 throw 语句提供一个 throwable 对象的实例。
例如,如果您只想抛出一个通用异常,可以使用以下语句:
throw new Exception();
如果,另一方面,您希望抛出特定异常,例如一个 NumberFormatException,您可以使用以下语句:
throw new NumberFormatException();
// or
throw new NumberFormatException( "this is some message" );
唯一需要的是理解您希望抛出的异常可用的构造函数。
示例
下面的 MyCalculator 类为客户端代码提供了访问 sum() 和 div() 方法的途径。sum() 和 div() 均接受两个字符串参数,分别代表两个整数。sum() 方法返回两个参数 int 等价形式的和,而 div() 方法返回两个参数 int 等价形式的商。
public class MyCalculator {
public int sum(String num1String, String num2String) {
int sum = 0;
try {
int num1 = Integer.parseInt(num1String);
int num2 = Integer.parseInt(num2String);
sum = num1 + num2;
}
catch (NumberFormatException nfe) {
throw new NumberFormatException();
}
return sum;
}
public int div(String num1String, String num2String) {
int div = 0;
try {
int num1 = Integer.parseInt(num1String);
int num2 = Integer.parseInt(num2String);
div = num1 / num2;
}
catch (NumberFormatException nfe) {
throw new NumberFormatException();
}
catch (ArithmeticException ae) {
throw new ArithmeticException();
}
return div;
}
}
使用上面的代码示例回顾 MyCalculator 类。注意该类如何使用以下语句:
throw new NumberFormatException();
并且
throw new ArithmeticException();
在适当的时候故意抛出每个 Exception。
因此,在编写测试类时,必须使用 try-catch 块来检查你的方法代码是否抛出了正确的异常。在你的 try 块中,你应该调用导致抛出异常的方法。catch 块应该捕获抛出的异常。然后你必须断言该异常存在、是正确类型的异常,并且(如果适用)包含正确的消息。
观察部分实现的测试类 MyCalculatorTest。该类将用于评估 MyCalculator 类,以确定该类是否为每个测试用例抛出了正确的异常。
注意测试类采用了上述方法,声明了一个与被测试异常相匹配的异常对象。最初该异常对象被设置为 null,仅在 catch 块中进行更新。
public class MyCalculatorTest extends student.TestCase {
MyCalculator calc;
public void setUp() {
calc = new MyCalculator();
}
/**
* Tests to ensure Sum throws a NumberFormatException
* if the first parameter is not a number
*/
public void testSumNFEException() {
NumberFormatException myNFE = null;
try {
calc.sum("2hello", "3");
}
catch (NumberFormatException nfe) {
myNFE = nfe;
}
assertNotNull(myNFE);
}
/**
* Tests to determine if div throws an ArithmeticException
* if one of the parameters is 0
*/
public void testDivArithException() {
ArithmeticException myAE = null;
try {
calc.div("2", "0");
}
catch (ArithmeticException ae) {
myAE = ae;
}
assertNotNull(myAE);
}
}
