异常

异常

目标

  • 学习异常处理基础知识
  • 了解异常分层结构和如何使用多个 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 开发的另一个优势。)

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

使用 try、catch 和 finally

清单 2 显示了使用标准的异常处理代码块从 清单 1 中清除的错误代码:try、catch 和 finally。

清单 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
  }
}

try、catch 和 finally 代码块共同形成了一张捕获异常的网。首先,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
  }
}

在这个示例中,FileNotFoundException 是 IOException 的子类,所以它必须放在 IOException catch 代码块的前面。IOException 是 Exception 的子类,所以它必须放在 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 会提醒您。

上一篇 下一篇