exception 的优点

既然您知道什么是异常以及如何使用它们,现在该学习在程序中使用异常的好处了。

优势 1:将错误处理代码与“常规”代码分开

异常提供了一种方法,可以将发生异常情况时应采取的措施细节与程序的主要逻辑分开。在传统编程中,错误检测,报告和处理常常导致混乱的意大利面条式代码。例如,在这里考虑将整个文件读入内存的伪代码方法。

readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}

乍一看,此功能似乎足够简单,但它忽略了以下所有潜在错误。

  • 如果无法打开文件怎么办?

  • 如果无法确定文件的 Long 度怎么办?

  • 如果无法分配足够的内存会怎样?

  • 如果读取失败怎么办?

  • 如果无法关闭文件会怎样?

要处理此类情况,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;
}

错误检测,报告和返回的地方太多了,原来的七行代码在混乱中丢失了。更糟糕的是,代码的逻辑流程也丢失了,因此很难判断代码是否在做正确的事情:如果函数无法分配足够的内存,文件是否真的关闭了?在编写方法三个月后修改该方法时,要确保代码 continue 正确执行操作就更加困难了。许多程序员通过简单地忽略它来解决该问题-当程序崩溃时会报告错误。

异常使您能够编写代码的主要流程,并处理其他地方的特殊情况。如果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内可能发生的错误感兴趣的唯一方法。传统的错误通知技术迫使method2method3readFile返回的错误代码传播到调用堆栈,直到错误代码final到达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 运行时环境在调用堆栈中向后搜索,以查找对处理特定异常感兴趣的所有方法。一个方法可以躲避其中抛出的任何异常,从而允许一个位于调用堆栈上方的方法来catch它。因此,只有关心错误的方法才需要担心检测错误。

method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}

但是,如伪代码所示,回避异常需要中间人方法方面的努力。必须在其throws子句中指定方法中可以引发的所有检查的异常。

优势 3:分组和区分错误类型

因为程序中抛出的所有异常都是对象,所以异常的分组或分类是类层次结构的自然结果。 Java 平台中一组相关的异常类的示例是在java.ioIOException及其后代中定义的那些异常类。 IOException是最通用的,代表执行 I/O 时可能发生的任何类型的错误。它的后代代表更具体的错误。例如,FileNotFoundException表示无法在磁盘上找到文件。

方法可以编写可以处理非常特定的异常的特定处理程序。 FileNotFoundException类没有后代,因此以下处理程序只能处理一种类型的异常。

catch (FileNotFoundException e) {
    ...
}

通过在catch语句中指定任何异常的超类,方法可以根据其组或常规类型catch异常。例如,要catch所有 I/O 异常,无论它们的具体类型如何,异常处理程序都会指定IOException参数。

catch (IOException e) {
    ...
}

该处理程序将能够catch所有 I/O 异常,包括FileNotFoundExceptionEOFException等。您可以通过查询传递给异常处理程序的参数来查找有关发生的详细信息。例如,使用以下命令打印堆栈跟踪。

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类层次结构的顶部。因此,此处理程序除了将catch该处理程序打算catch的那些异常之外,还将catch许多其他异常。如果您要让程序执行的所有操作(例如,为用户打印一条错误消息然后退出),则可能需要用这种方式处理异常。

但是,在大多数情况下,您希望异常处理程序尽可能具体。原因是处理程序必须做的第一件事是确定发生哪种类型的异常,然后才能决定最佳的恢复策略。实际上,通过不catch特定错误,处理程序必须适应任何可能性。太笼统的异常处理程序可以通过catch和处理程序员未预料到的,处理程序不打算使用的异常来使代码更易于出错。

如前所述,您可以创建异常组并以常规方式处理异常,也可以使用特定的异常类型来区分异常并以精确的方式处理异常。