是它却忽略了所发生下面这些错误的可能。
1、
如果不能打开文件,会发生什么?
2、
如果不能判定文件的大小,会发生什么?
3、
如果没有足够的内存,会发生什么?
4、
如果读取失败,会发生什么?
5、
如果文件不能关闭。会发生什么?
要处理这些信息, readFile 函数必须用更多的代码来做错误发现、报告和处理工作。这个函数看上去可能象这样:
errorCodeType readFile {
initialize errorCode = 0;
open the file;
if (theFileIsOpen) {
determine the length of the file;
if (gotTheFileLength) {
allocate that much memory;
if (gotEnoughMemory) {
read the file into memory;
if (readFailed) {
errorCode = -1;
}
else {
errorCode = -2;
}
} else {
errorCode = -3;
}
close the file;
if (theFileDidntClose && errorCode == 0) {
errorCode = -4;
} else {
errorCode = errorCode and -4;
}
} else {
errorCode = -5;
}
return errorCode;
}
有如此多的错误发现、报告和返回,使得初的 7 行代码被埋没在混乱的错误代码之中。更严重的是,代码的逻辑流已经没有了,这样使得它很难说明代码是否正在做着正确的事情:如果函数在分配内存过程失败,文件真得的被关闭了吗?甚至更难保证在三个月之后,你编写的这段代码继续做正确的事情。
异常处理使你能够编写代码的主工作流并且在别的地方来处理异常信息。如果 readFile 函数使用异常处理来代替传统的错误管理技术,它应该像如下所示的代码这样:
readFile {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
注意:异常处理不会节省错误的发现、报告、处理的工作量,但是它们能够帮助你更有效的组织代码。
优势 2 :向调用堆栈上层传递错误
异常处理的第二个优势是向方法的调用堆栈上层传递错误报告的能力。假如 readFile 方法是主程序调用的一系列嵌套方法中的第四个方法:方法 1 调用方法 2 ,方法 2 调用方法 3 ,方法 3 调用 readFile ,代码结构如下所示:
method1 {
call method2;
}
method2 {
call method3;
}
method3 {
call readFile;
}
还假如 method1 是唯一的能够处理 readFile 方法中所可能发生的错误的方法,那么传统的错误处理技术会强制 method2 和 method3 来传递通过 readFile 调用堆栈所返回的错误代码,直到错误代码传递到 method1 -因为只有 method1 能够处理这些错误,其代码结构如下所示:
method1 {
errorCodeType error;
error = call method2;
if (error)
doErrorProcessing;
else
proceed;
}
errorCodeType method2 {
errorCodeType error;
error = call method3;
if (error)
return error;
else
proceed;
}
errorCodeType method3 {
errorCodeType error;
error = call readFile;
if (error)
return error;
else
proceed;
}
回忆一下, Java 运行时环境搜寻调用堆栈来查找任意的处理特殊的异常的方法。一个方法能够抛出它内部的任何异常,所以允许一个上层调用堆栈的方法来捕获它。因此只有处理相关错误的方法来处理发现的错误,代码结构如下所示:
method1 {
try {
call method2;
} catch (exception e) {
doErrorProcessing;
}
}
method2 throws exception {
call method3;
}
method3 throws exception {
call readFile;
}
无论怎样,就像伪代码所展示的那样,躲避异常需要中间方法做一些工作。任意被检查到的由内部方法的抛出的异常必须在这个方法的 throws 子句中被指定。
优势 3 :分组和区分错误类型
因为所有在程序内部抛出的异常都是对象,异常的分组或分类是类继承的自然结果。在 Java 平台中一组相关异常类的例子是在 java.io 中定义的 IOException 和它的子类。 IOException 是最普通的 IO 异常管理类,并且它描述了在执行 I/O 操作时所发生的任意的错误类型。它的子类描述了一些特殊的错误。例如, FileNotFoundException 异常类代表不能在本地磁盘上找到一个文件。
一个方法能够编写特殊的异常处理器,使它能够处理非常特殊的异常。 FileNotFoundException 异常类没有子类,因此下面的异常处理器只能处理一种异常类型:
catch (FileNotFoundException e) {
...
}
一个方法能够基于它的分组或通过在 catch 子句中所指定的任何异常的超类的一般类型来捕获异常。例如,要捕获所有的 I/O 异常,而不管它们的具体类型,就可以在异常处理器中指定一个 IOException 参数:
catch (IOException e) {
...
}
这个处理器将捕获所有的 I/O 异常,包括 FileNotFoundException,EOFException 等等。你能够通过查询传递给异常处理器的参数找到发生错误的详细信息。例如,打印堆栈执行路线:
catch (IOException e) {
e.printStackTrace(); // output goes to Sytem.err
e.printStackTrace(System.out); // send trace to stdout
}
你甚至可以创建一个能够处理任意类型的异常的异常处理器:
catch (Exception e) { // a (too) general exception handler
...
}
Exception 类是 Throwable 类结构中的顶级类,因此,这个处理器将捕获除了那些被特定处理器捕获的异常以外的异常。你可能想你的程序是否都是这种处理异常的方法,例如,为用户打印错误消息并且退出。
但是,在大多数情况下