内容


第 14 单元:异常

使用内置 Java 平台机制处理代码中的错误

Comments

开始之前

本单元是 “Java 编程入门” 学习路径的一部分。尽管各单元中讨论的概念具有独立性,但实践组件是在您学习各单元的过程中逐步建立起来的,推荐您在继续学习之前复习 前提条件、设置和单元细节

单元目标

  • 学习异常处理基础知识
  • 了解异常分层结构和如何使用多个 catch 代码块

异常处理基础

没有程序能够始终正常运行,Java 语言的设计者也知道这一点。Java 平台提供了内置机制来处理代码未准确地按计划运行的情形。

异常 是在程序执行期间发生的破坏正常的程序指令流的事件。异常处理是 Java 编程的一项基础技术。您将代码包装在一个 try 代码块中(这表示 “尝试此代码并让我知道它是否导致了异常”),并使用它捕获 (catch) 各种类型的异常。

要开始执行异常处理,可以看看清单 1 中的代码。

清单 1. 您发现错误了吗?
@Test
public void yetAnotherTest() {
  Logger l = Logger.getLogger(Employee.class.getName());
//    Employee employee1 = new Employee();
  Employee employee1 = null;
  employee1.setName("J Smith");
  Employee employee2 = new Employee();
  employee2.setName("J Smith");
  l.info("Q: employee1 == employee2?      A: " + (employee1 == employee2));
  l.info("Q: employee1.equals(employee2)? A: " + employee1.equals(employee2));
}

请注意,Employee 引用被设置为 null。运行此代码,您会获得以下输出:

java.lang.NullPointerException
  at com.makotojava.intro.EmployeeTest.yetAnotherTest(EmployeeTest.java:49)
  .
  .
  .

此输出会告诉您,您在尝试通过一个 null 引用(指针)来引用一个对象,这是一个非常严重的开发错误。(您可能已注意到,Eclipse 通过下面这条消息向您提醒潜在的错误:Null pointer access:The variable employee1 can only be null at this location.Eclipse 向您提醒许多潜在的开发错误,这也是使用 IDE 执行 Java 开发的另一个优势。)

幸运的是,可以使用 trycatch 代码块(以及来自 finally 的一些帮助)捕获错误。

使用 try、catch 和 finally

清单 2 显示了使用标准的异常处理代码块从 清单 1 中清除的错误代码:trycatchfinally

清单 2. 捕获一个异常
@Test
public void yetAnotherTest() {
  Logger l = Logger.getLogger(Employee.class.getName());

  //    Employee employee1 = new Employee();
  try {
    Employee employee1 = null;
    employee1.setName("J Smith");
    Employee employee2 = new Employee();
    employee2.setName("J Smith");
    l.info("Q: employee1 == employee2?      A: " + (employee1 == employee2));
    l.info("Q: employee1.equals(employee2)? A: " + employee1.equals(employee2));
  } catch (Exception e) {
    l.severe("Caught exception: " + e.getMessage());
  } finally {
    // Always executes
  }
}

trycatchfinally 代码块共同形成了一张捕获异常的网。首先,try 语句限定可能抛出异常的代码。在该例子中,异常直接放在 catch 代码块或异常处理函数 中。在所有尝试和捕获都完成后,会继续执行 finally 代码块,无论是否发生了异常。捕获到异常时,可以尝试优雅地进行恢复,也可以退出程序(或方法)。

清单 2 中,程序从错误中恢复,然后打印异常的消息:

Sep 19, 2015 2:01:22 PM com.makotojava.intro.EmployeeTest yetAnotherTest
SEVERE: Caught exception: null

异常分层结构

Java 语言包含一个完整的异常分层结构,它由许多类型的异常组成,这些异常划分为两大主要类别:

  • 已检查的异常已由编译器检查(表示编译器确定它们已在您代码中的某处处理过)。一般而言,这些异常是 java.lang.Exception 的直接子类。
  • 未检查的异常(也称为运行时异常)未由编译器检查。这些是 java.lang.RuntimeException 的子类。

程序导致异常时,您可以说它抛出了 异常。已检查的异常可由任何方法在方法名中包含 throws 关键字来声明。后跟该方法可能在执行期间抛出的异常的逗号分隔列表。如果代码调用的一个方法指定它抛出一种或多种类型的异常,您必须对它进行一定的处理,或者向方法名中添加一个 throws 来传递该异常类型。

发生异常时,Java 运行时在堆栈中往上搜索一个异常处理函数。如果在到达堆栈顶部时仍未找到异常处理函数,它会立即终止程序,就像在 清单 1 中看到的那样。

多个 catch 代码块

可以拥有多个 catch代码块,但必须采用某种特定方式来搭建它们。如果所有异常都是其他异常的子类,那么子类会按照 catch 代码块的顺序放在父类前面。清单 3 给出了一个按正确的分层结构顺序搭建的不同异常类型的示例。

清单 3. 异常分层结构示例
@Test
public void exceptionTest() {
  Logger l = Logger.getLogger(Employee.class.getName());
  File file = new File("file.txt");
  BufferedReader bufferedReader = null;
  try {
    bufferedReader = new BufferedReader(new FileReader(file));
    String line = bufferedReader.readLine();
    while (line != null) {
      // Read the file
    }
  } catch (FileNotFoundException e) {
    l.severe(e.getMessage());
  } catch (IOException e) {
    l.severe(e.getMessage());
  } catch (Exception e) {
    l.severe(e.getMessage());
  } finally {
    // Close the reader
  }
}

在这个示例中,FileNotFoundExceptionIOException 的子类,所以它必须放在 IOException catch 代码块的前面。IOExceptionException 的子类,所以它必须放在 Exception catch 代码块的前面。

try-with-resources 代码块

清单 3 中的代码必须声明一个变量来包含 bufferedReader 引用,然后在 finally 中必须关闭 BufferedReader

try 代码块超出范围时,更加紧凑的语法(从 JDK7 开始提供)也可以自动关闭资源。清单 4 给出了这种更新的语法。

清单 4. 资源管理语法
@Test
public void exceptionTestTryWithResources() {
  Logger l = Logger.getLogger(Employee.class.getName());
  File file = new File("file.txt");
    try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
      String line = bufferedReader.readLine();
      while (line != null) {
// Read the file
      }
    } catch (Exception e) {
      l.severe(e.getMessage());
    }
}

基本上讲,在圆括号内的 try 后分配资源变量,在 try 代码块超出范围时,这些资源会自动关闭。这些资源必须实现 java.lang.AutoCloseable 接口;如果尝试在一个没有实现该接口的资源类上实现此语法,Eclipse 会提醒您。

测试您的理解情况

  1. 以下哪个有关异常的描述是正确的?
    1. 异常是 Java 程序中反常的执行流。
    2. 已检查的异常未被编译器检查,所以您必须自行检查该异常。
    3. 您必须在方法签名上使用 throws 关键字指定所有运行时异常。
    4. 上述选项都不是。
    5. 上述所有选项。
  2. finally 代码块每次都会执行,无论是否抛出了异常。
  3. 以下代码是否会被编译?解释您的答案。
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.logging.Logger;
    
    public class Test1 {
       
       private static final Logger l = Logger.getLogger(Test1.class.getName());
       
       public void hierarchyExample() {
          File file = new File("file.txt");
          
          BufferedReader bufferedReader = null;
          
          try {
             bufferedReader = new BufferedReader(new FileReader(file));
             String line = bufferedReader.readLine();
             while (line != null) {
                // Read the line
             }
          } catch (IOException e) {
             l.severe(e.getMessage());
          } catch (FileNotFoundException e) {
    
             l.severe(e.getMessage());
          } catch (Exception e) {
             l.severe(e.getMessage());
          }
       }
    
    }
  4. try-with-resources 代码块中使用的类必须实现以下哪个接口?
    1. java.lang.Iterable
    2. java.lang.AutoClosable
    3. java.lang.Iterablejava.io.Closable
    4. java.lang.AutoCloseablejava.lang.Exception
    5. 上述选项都不是。
  5. 不需要向编译器指定未检查的异常,而且可在运行时抛出。
  6. 以下哪个 Exception 子类是未检查的异常?使用 “进一步探索” 部分的参考资料和 JDK Javadoc 帮助寻找答案。
    1. java.lang.IllegalArgumentExeption
    2. java.sql.SQLException
    3. java.time.DateTimeException
    4. java.lang.IllegalArgumentException
    5. 上述所有选项。
  7. 如果在执行您编写的方法时抛出已检查异常,则必须进行哪些特殊考虑?
    1. 确保已在方法的前面使用 throws 关键字声明该异常,或者捕获并处理方法中的异常。
    2. 无。已检查的异常仅被运行时检查,不会被编译器检查。
    3. 确保在所有受影响的代码中使用 finally 代码块。
    4. 不存在所谓的已检查异常。
    5. 上述选项都不是。
  8. 哪一项会告诉编译器,某个异常属于未检查异常?
    1. 编译器无法知道异常是否已检查,这使异常成为 Java 中的一个难题。
    2. 未检查异常是 java.lang.Exception 的直接子类。
    3. 不存在所谓的未检查异常。
    4. 编译器不会检查未检查异常。
    5. 上述选项都不是。

核对您的答案

进一步探索

Java 教程:异常

Java - 异常

Java 教程:未检查异常 — 存在的争议

Java:解释已检查与未检查异常

上一单元:对象的后续处理下一单元:构建 Java 应用程序


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=1038946
ArticleTitle=第 14 单元:异常
publish-date=10252016