MengBridgeCourse 中文版

第 7 章 测试与异常

| 关于   英文版   «  7.1. 回顾 Java 单元测试   ::   目录   ::   7.3. 对象、枚举和 UML 图  »

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。

下载与视频对应的幻灯片。观看视频时对其做笔记,并亲自练习绘制图表!

Video Slides 7.2.2-IntroToExceptions.pdf

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);
    }

}

7.2.9. 检查点 2

   «  7.1. 回顾 Java 单元测试   ::   目录   ::   7.3. 对象、枚举和 UML 图  »

关闭窗口