例外的优点
原文: https://docs.oracle.com/javase/tutorial/essential/exceptions/advantages.html
现在您已经知道了什么是例外以及如何使用它们,现在是时候了解在程序中使用异常的优势了。
优点 1:将错误处理代码与“常规”代码分开
异常提供了一种方法,可以在程序的主要逻辑中分离出异常情况时要执行的操作的详细信息。在传统的编程中,错误检测,报告和处理通常会导致混乱的意大利面条代码。例如,考虑这里的伪代码方法将整个文件读入内存。
readFile {
_open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;_
}
乍一看,这个功能似乎很简单,但它忽略了以下所有潜在的错误。
- 如果无法打开文件会怎么样?
- 如果无法确定文件的长度会发生什么?
- 如果无法分配足够的内存会怎样?
- 如果读取失败会发生什么?
- 如果文件无法关闭会发生什么?
要处理此类情况,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;
}
这里有很多错误检测,报告和返回,原始的七行代码在杂乱中丢失了。更糟糕的是,代码的逻辑流程也已丢失,因此很难判断代码是否正在做正确的事情:如果函数无法分配足够的内存,文件是否真的被关闭了?在编写方法三个月后修改方法时,确保代码继续做正确的事情变得更加困难。许多程序员通过忽略它来解决这个问题 - 当程序崩溃时会报告错误。
例外使您可以编写代码的主流并处理其他地方的异常情况。如果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
方法是主程序进行的一系列嵌套方法调用中的第四种方法:method1
调用method2
,调用method3
,最后调用readFile
。
method1 {
_call method2;_
}
method2 {
_call method3;_
}
method3 {
_call readFile;_
}
假设method1
是唯一对readFile
中可能发生的错误感兴趣的方法。传统的错误通知技术强制method2
和method3
将readFile
返回的错误代码传播到调用堆栈,直到错误代码最终达到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
是最常用的,表示执行 I / O 时可能发生的任何类型的错误。它的后代表示更具体的错误。例如,FileNotFoundException
表示文件无法位于磁盘上。
方法可以编写可以处理非常特定异常的特定处理器。 FileNotFoundException
类没有后代,因此以下处理器只能处理一种类型的异常。
catch (FileNotFoundException e) {
...
}
方法可以通过在catch
语句中指定任何异常的超类来基于其组或常规类型捕获异常。例如,要捕获所有 I / O 异常,无论其特定类型如何,异常处理器都指定IOException
参数。
catch (IOException e) {
...
}
此处理器将能够捕获所有 I / O 异常,包括FileNotFoundException
,EOFException
等。您可以通过查询传递给异常处理器的参数来查找有关所发生情况的详细信息。例如,使用以下命令打印堆栈跟踪。
catch (IOException e) {
// Output goes to System.err.
e.printStackTrace();
// Send trace to stdout.
e.printStackTrace(System.out);
}
你甚至可以设置一个异常处理器来处理任何带有处理器的Exception
。
// A (too) general exception handler
catch (Exception e) {
...
}
Exception
类靠近Throwable
类层次结构的顶部。因此,除了处理器要捕获的那些异常之外,此处理器还将捕获许多其他异常。如果您希望程序执行所有操作,您可能希望以这种方式处理异常,例如,为用户打印出错误消息然后退出。
但是,在大多数情况下,您希望异常处理器尽可能具体。原因是处理器必须做的第一件事是确定在确定最佳恢复策略之前发生了什么类型的异常。实际上,通过不捕获特定错误,处理器必须适应任何可能性。过于笼统的异常处理器可以通过捕获和处理器员未预料到并且处理器不是意图的异常来使代码更容易出错。
如上所述,您可以创建异常组并以一般方式处理异常,或者您可以使用特定的异常类型来区分异常并以精确的方式处理异常。