任何程序都无法始终正常运行,Java 语言的设计者完全了解这一点。在本节中,我会介绍 Java 平台的各种内置机制,处理代码未准确地按计划运行的情况。
异常处理基础
异常 是在程序执行期间发生的、可破坏正常程序指令流的事件。异常处理是 Java 编程的一项基础技术。其基本机制是,您将代码包装在一个 try
代码块中(这表示”尝试此代码并让我知道它是否导致了异常”),并使用它 catch
各种类型的异常。
要想开始执行异常处理,可查看清单 10 中的代码。
清单 10. 您发现错误了吗?
@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.nowjava.intro.EmployeeTest.yetAnotherTest(EmployeeTest.java:49)
.
.
.
此输出告诉您,您正在尝试通过一个 null
引用(指针)来引用一个对象,这是一个非常严重的开发错误。
幸运的是,可使用 try
和 catch
代码块捕获它(还有来自 finally
的一些帮助)。
try、catch 和 finally
清单 11 显示了使用标准异常处理代码块从 清单 10 中清除的错误代码:try
、catch
和 finally
。
清单 11. 捕获一个异常
@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
代码块或 异常处理函数 中。在所有 try 和 catch 都完成后,会继续执行 finally
代码块,无论是否抛出了异常都是如此。捕获到一个异常时,您可尝试优雅地从异常中恢复,或者退出程序(或方法)。
在清单 11 中,程序从错误中恢复,然后打印出异常的消息:
Sep 19, 2015 2:01:22 PM com.nowjava.intro.EmployeeTest yetAnotherTest
SEVERE:Caught exception: null
异常分层结构
Java 语言包含一个完整的异常分层结构,它由多种类型的异常组成,这些异常主要分为两类:
- 已检查的异常 已由编译器检查(表示编译器确定已在代码中的某处处理过这些异常)。通常,这些是
java.lang.exception
的直接子类。 - 未检查的异常 (也称为 运行时异常 )未由编译器检查。这些是
java.lang.RuntimeException
的子类。
程序导致异常时,您可以说它 抛出了 异常。任何方法都可在方法签名中包含 throws
关键字,从而向编译器声明已检查的异常。接下来是一个该方法可能在执行期间抛出的各种异常的逗号分隔列表。如果代码所调用的一个方法指定它抛出一种或多种类型的异常,就必须对它进行一定的处理,或者向方法签名中添加一个 throws
来传递该异常类型。
发生异常时,Java 运行时在堆栈中向上搜索异常处理函数。如果到达堆栈顶部时仍未找到异常处理函数,它会立即终止程序,就像在清单 10 中看到的一样。
多个 catch 代码块
您可拥有多个 catch
代码块,但必须以特定的方式来搭建它们。如果任何异常是其他异常的子类,那么子类按 catch
代码块的顺序放在父类前面。清单 12 显示了按正确的分层结构顺序搭建的不同异常类型示例。
清单 12. 异常分层结构示例
@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 代码块
清单 12 中的代码必须声明一个变量来包含 bufferedReader
引用,然后在 finally
中必须关闭 BufferedReader
。