文章目录
- 前言
- 一、处 理 错 误
- 1.异常分类
- 2.声明受查异常
- 3.如何抛出异常
- 4.创建异常类
- 二、捕获异常
- 1.捕获异常
- 2.捕获多个异常
- 3.再次抛出异常与异常链
- 4.finally 子句
- 5.带资源的 try 语句
- 三、使用断言
- 1.断言的概念
- 2.启用和禁用断言
- 3.使用断言完成参数检查
- 总结
前言
在现实世界中却充满了不良的数据和带有问题的代码,现在是讨论 Java 程序设计语育处理这些问题的机制的时候了。对于异常情况, 例如, 可能造成程序崩溃的错误输入,Java 使 用 一 种 称 为 异 常 处 理
( exception handing) 的错误捕获机制处理。Java 中的异常处理与 C++ 或 Delphi 中的异常处理十分类似。
一、处 理 错 误
假设在一个 Java 程序运行期间出现了一个错误。这个错误可能是由于文件包含了错误信息,或者网络连接出现问题造成的,也有可能是因为使用无效的数组下标, 或者试图使用一个没有被赋值的对象引用而造成的。用户期望在出现错误时, 程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成, 程序应该:
- 返回到一种安全状态,并能够让用户执行一些其他的命令;
- 允许用户保存所有操作的结果,并以妥善的方式终止程序;
要做到这些并不是一件很容易的事情。其原因是检测(或引发)错误条件的代码通常离那些能够让数据恢复到安全状态, 或者能够保存用户的操作结果, 并正常地退出程序的代码很远。异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
1.异常分类
在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例。如果 Java 中内置的异常类不能够满足需求,用户可以创建自己的异常类。
需要注意的是,所有的异常都是由 Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception。
Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。 如果出现了这样的内部错误, 除了通告给用户,并尽力使程序安全地终止之外, 再也无能为力了。这种情况很少出现。
在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常。
Java 语 言 规 范 将 派 生 于 Error 类 或 RuntimeException 类的所有异常称为非受查( unchecked ) 异常,所有其他的异常称为受查( checked) 异常。编译器将核查是否为所有的受査异常提供了异常处理器
。
2.声明受查异常
如果遇到了无法处理的情况, 那么 Java 的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如,一段读取文件的代码知道有可能读取的文件不存在, 或者内容为空,因此, 试图处理文件信息的代码就需要通知编译器可能会抛出 IOException 类的异常。
方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受査异常。例如,下面是标准类库中提供的 FilelnputStream 类的一个构造器的声明:
public FilelnputStream(String name) throws FileNotFoundException
这个声明表示这个构造器将根据给定的 String 参数产生一个 FilelnputStream 对象,但也有可能抛出一个 FileNotFoimdExc印tion 异常。如果发生了这种糟糕情况, 构造器将不会初始化一个新的 FilelnputStream 对象, 而是抛出一个 FileNotFoundException 类对象。 如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器, 以便知道如何处理FileNotFoundException 对象„
在自己编写方法时, 不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用 throws 子句声明异常, 什么异常必须使用 throws 子句声明, 需要记住在遇到下面 4 种情况时应该抛出异常:
- 调用一个抛出受査异常的方法, 例如, FilelnputStream 构造器。
- 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常。
- 程序出现错误, 例如,a[-1] =0 会抛出一个 ArraylndexOutOffloundsException 这样的非受查异常。
- Java 虚拟机和运行时库出现的内部错误。
如果出现前两种情况之一, 则必须告诉调用这个方法的程序员有可能抛出异常。因为任何一个抛出异常的方法都有可能是一个死亡陷阱。 如果没有处理器捕获这个异常,当前执行的线程就会结束。
对于那些可能被他人使用的 Java 方法, 应该根据异常规范( exception specification), 在方法的首部声明这个方法可能抛出的异常。如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
public class MyAnimation {
public Image loadlmage(String s) throws FileNotFoundException, EOFException {
...
}
}
总之,一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制( Error),要么就应该避免发生( RuntimeException)。如果方法没有声明所有可能发生的受查异常, 编译器就会发出一个错误消息。
3.如何抛出异常
假设在程序代码中发生了一些很糟糕的事情。 一个名为 readData 的方法正在读取一个首部具有下列信息的文件:Content-length: 1024
然而,读到 733 个字符之后文件就结束了。我们认为这是一种不正常的情况,希望抛出一个异常。仔细地阅读 Java API 文档之后会发现:EOFException 异常描述的是“ 在输人过程中, 遇到了一个未预期的 EOF 后的信号”。这正是我们要抛出的异常。
String readData(Scanner in) throws EOFException{
if (Mn.hasNextQ){
if (n < len){
throw new EOFException();
}
}
}
4.创建异常类
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。 在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception 的类,或者派生于 Exception 子类的类。
public class FileFormatException extends IOException {
public FileFormatException() {
}
public FileFormatException(String message) {
super(message);
}
}
public class MyAnimation {
public static void main(String[] args) throws FileFormatException {
throw new FileFormatException("我是创建异常类");
}
}
二、捕获异常
到目前为止, 已经知道如何抛出一个异常。这个过程十分容易。只要将其抛出就不用理踩了。当然, 有些代码必须捕获异常。
1.捕获异常
要想捕获一个异常, 必须设置 try/catch语句块。最简单的 try语句块如下所示:
try {
code
} catch (ExceptionType e) {
handlerfor this type
}
如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类, 那么
- 程序将跳过 try语句块的其余代码。
- 程序将执行 catch 子句中的处理器代码。
2.捕获多个异常
在一个 try 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的 catch 子句:
try {
//code that might throwexceptions
} catch (FileNotFoundException e) {
//emergencyactionfor missingfiles
} catch (UnknownHostException e) {
//emergency actionfor unknown hosts
} catch (IOException e) {
//emergencyactionfor all other I/O problems
}
异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息, 可以试着使用 e.getHessage() 得到详细的错误信息(如果有的话) 或者使用 e.getClassO.getNameO得到异常对象的实际类型。
在 Java SE 7中,同一个 catch 子句中可以捕获多个异常类型。例如,假设对应缺少文件和未知主机异常的动作是一样的,就可以合并 catch 子句:
try {
//code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
//emergency action for missing files and unknown hosts
} catch (IOException e) {
//emergency action for all other I / O problems
}
3.再次抛出异常与异常链
在 catch 子句中可以抛出一个异常,这样做的目的是改变异常的类型: 如果开发了一个供其他程序员使用的子系统, 那么,用于表示子系统故障的异常类型可能会产生多种解释。ServletException 就是这样一个异常类型的例子。执行 servlet 的代码可能不想知道发生错误的细节原因, 但希望明确地知道 servlet 是否有问题。下面给出了捕获异常并将它再次抛出的基本方法:
try {
//access the database
} catch (SQLException e) {
throw new ServletException("database error: " + e.getMessageO);
}
4.finally 子句
当代码抛出一个异常时, 就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
Java 有一种好的解决方案,这就是 finally 子句。下面将介绍 Java 中如何恰当地关闭一个文件。如果使用 Java 编写数据库程序,就需要使用同样的技术关闭与数据库的连接。
不管是否有异常被捕获,finally 子句中的代码都被执行
。在下面的示例中, 程序将在所有情况下关闭文件。
InputStream in;
try {
in = new FileInputStream("");
//code that might throwexceptions
} catch (IOException e) {
e.printStackTrace();
//showerror message
} finally {
in.close();
}
5.带资源的 try 语句
对于以下代码模式:
open a resource
try {
work with the resource
}
finally {
close the resource
}
假设资源属于一个实现了 AutoCloseable 接口的类,Java SE 7 为这种代码模式提供了一个很有用的快捷方式。AutoCloseable 接口有一个方法:void close() throws Exception
带资源的 try 语句(try-with-resources) 的最简形式为:
try (Resource res = . . .)
{
work with res
}
try块退出时,会自动调用 res.close()。
三、使用断言
在一个具有自我保护能力的程序中, 断言很常用。
1.断言的概念
假设确信某个属性符合要求, 并且代码的执行依赖于这个属性。例如, 需要计算
double y = Math.sqrt(x);
我们确信,这里的 X 是一个非负数值。原因是:X 是另外一个计算的结果,而这个结果不可能是负值;或者 X 是一个方法的参数,而这个方法要求它的调用者只能提供一个正整数。然而,还是希望进行检查, 以避免让“ 不是一个数” 的数值参与计算操作。当然,也可以抛出一个异常:
if (x < 0) throw new 111egalArgumentException(“x < 0”);
但是这段代码会一直保留在程序中, 即使测试完毕也不会自动地删除。如果在程序中含有大量的这种检查,程序运行起来会相当慢。
断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。
Java 语言引人了关键字 assert。这个关键字有两种形式:
- assert 条件;
- assert 条件:表达式;
2.启用和禁用断言
在默认情况下, 断言被禁用。可以在运行程序时用 -enableassertions 或 -ea 选项启用:
java -enableassertions MyApp
也可以用选项 -disableassertions 或 -da 禁用某个特定类和包的断言:
java -ea:… -da:MyClass MyApp
3.使用断言完成参数检查
什么时候应该选择使用断言呢? 请记住下面几点:
- 断言失败是致命的、 不可恢复的错误。
- 断言检查只用于开发和测阶段
因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。
总结
以上就是关于异常和断言的内容,我们应该正确处理程序异常。