已检查或者未检查的异常?

时间:2020-01-09 10:35:57  来源:igfitidea点击:

在Java中,基本上有两种类型的异常:已检查的异常和未检查的异常。 Conly具有未经检查的异常。已检查和未检查的异常之间的区别是:

  • 必须按照基本try-catch-finally异常处理中的描述显式捕获或者传播已检查的异常。未检查的异常没有此要求。它们不必被捕获或者声明为抛出。
  • Java中的检查异常扩展了java.lang.Exception类。未经检查的异常扩展了java.lang.RuntimeException。

支持和反对checked和unchecked以及是否完全使用checked异常都有很多参数。在本文中,我将介绍最常见的论点。在我这样做之前,让我先说清楚一件事:

Checked and unchecked exceptions are functionally equivalent. There is
    nothing you can do with checked exceptions that cannot also be done with unchecked
    exceptions, and vice versa.

无论我们是在检查的异常还是未检查的异常之间进行选择,这都是个人风格或者组织风格的问题。在功能上没有一个比另一个更好。

一个简单的例子

在讨论可检查和未检查异常的优缺点之前,我将向我们展示它们所造成的代码差异。这是一个引发检查异常的方法,以及另一个调用它的方法:

public void storeDataFromUrl(String url){
        try {
            String data = readDataFromUrl(url);
        } catch (BadUrlException e) {
            e.printStackTrace();
        }
    }

    public String readDataFromUrl(String url)
    throws BadUrlException{
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and return
        //it as a String instance.

        return data;
    }

如我们所见,readDataFromUrl()方法将引发BadUrlException。我自己创建了BadUrlException。 BadUrlException是一个已检查的异常,因为它扩展了java.lang.Exception:

public class BadUrlException extends Exception {
        public BadUrlException(String s) {
            super(s);
        }
    }

如果storeDataFromUrl()要调用readDataFromUrl(),则只有两个选择。它或者捕获BadUrlException,或者将其传播到调用堆栈。上面列出的storeDataFromUrl()捕获异常。此storeDataFromUrl()实现改为传播BadUrlException:

public void storeDataFromUrl(String url)
    throws BadUrlException{
        String data = readDataFromUrl(url);
    }

注意try catch块是如何消失的,而是添加了" throws BadUrlException"声明。现在,让我们看一下未经检查的异常的外观。首先,我更改BadUrlException来扩展java.lang.RuntimeException:

public class BadUrlException extends RuntimeException {
        public BadUrlException(String s) {
            super(s);
        }
    }

然后,我更改方法以使用现在未经检查的BadUrlException:

public void storeDataFromUrl(String url){
        String data = readDataFromUrl(url);
    }

    public String readDataFromUrl(String url) {
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and
        //return it as a String instance.

        return data;
    }

请注意,readDataFromUrl()方法如何不再声明它抛出BadUrlException。 storeDataFromUrl()方法也不必捕获BadUrlException。 storeDataFromUrl()方法仍然可以选择捕获异常,但不再必须捕获该异常,并且不再需要声明它传播了异常。

已检查或者未检查的异常?

既然我们已经看到了检查和未检查的异常之间的代码差异,那么让我们深入了解支持和反对这两个参数。

一些涵盖异常的Java书籍(\ *)建议我们对应用程序可以从中恢复的所有错误使用已检查的异常,对于应用程序无法从中恢复的错误使用非检查的异常。实际上,大多数应用程序必须从几乎所有异常中恢复,包括NullPointerException,IllegalArgumentException和许多其他未经检查的异常。失败的操作/事务将被中止,但应用程序必须保持活动状态并准备为下一个操作/事务提供服务。通常只有在启动期间关闭应用程序才是合法的。例如,如果缺少配置文件,并且没有该配置文件,应用程序将无法执行任何明智的操作,则关闭该应用程序是合法的。

(\ *)Suns Java教程仅此一项。

我对建议是仅使用检查的异常或者仅使用未检查的异常。混合使用异常类型通常会导致混乱和用法不一致。当然,我们应该务实。在我们所处的环境中做有意义的事情。

以下是支持和反对已检查和未检查的异常的最常见参数的列表。支持一种类型的异常的参数通常与另一种类型相对(赞成检查=未选中,赞成未经检查=选中)。因此,仅出于支持检查或者未检查的异常而将参数列出。

  • Pro Checked Exceptions:编译器强制捕获或者传播Checked异常,使得更难忘处理该异常。
  • Pro Checked异常:Unchecked异常使我们更容易忘记处理错误,因为编译器不会强制开发人员捕获或者传播异常(反1)。
  • Pro Unchecked Exceptions:在调用堆栈中传播的Checked异常会使顶级方法变得混乱,因为这些方法需要声明引发所有从其调用的方法引发的异常。
  • Pro Checked Exceptions:当方法未声明可能引发的非检查异常时,处理它们将变得更加困难。
  • Pro Unchecked Exceptions:抛出的检查异常成为方法接口的一部分,这使得在类或者接口的更高版本中很难从方法中添加或者删除异常。

每个参数也都有对应的参数,以下各节中将对这些参数进行讨论。

参数1(Pro Checked例外):

编译器强制捕获或者传播检查后的异常使得更难忘记对该异常的处理。

相反的观点:

当被迫捕获或者传播许多异常时,开发人员冒着草率行事的风险,只是写

try{
   callMethodThatThrowsException();
catch(Exception e){
}

从而有效地忽略了错误。

参数2(Pro Checked例外):

未检查的异常使我们更容易忘记处理错误,因为编译器不会强制开发人员捕获或者传播异常。

反议1:

当被迫处理或者传播检查的异常时,这并不比草率的异常处理趋势更糟。

反议2:

在最近的一个更大的项目中,我们决定使用未经检查的异常。我从该项目获得的个人经验是:使用未检查的异常时,任何方法都可能引发异常。因此,无论我在代码的哪个部分工作,我总是对例外情况有一定的意识。不仅在声明检查异常时。

此外,许多未声明任何检查异常的标准Java API方法仍可能会引发未检查异常,例如NullPointerException或者InvalidArgumentException。应用程序仍将需要处理这些未经检查的异常。我们可能会争辩说,存在检查异常的事实使我们很容易忘记处理未检查异常,因为未声明异常。

参数3(专业版未经检查的异常):

在调用堆栈中传播的已检查异常将使顶级方法混乱,因为这些方法需要声明引发从其调用的方法引发的所有异常。那是。声明的异常将聚集在调用堆栈中的方法中。例子:

public long readNumberFromUrl(String url)
    throws BadUrlExceptions, BadNumberException{
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }

如我们所见,readNumberFromUrl()需要声明引发从readDataFromUrl()和converData()方法引发的BadUrlException和BadNumberException。想象一下,在具有数千个类的应用程序的顶级方法中,需要声明多少个异常。这可能会使检查异常传播成为真正的难题。

反议1:

异常声明聚合很少在实际应用程序中发生。开发人员通常会改用异常包装。看起来是这样的:

public void readNumberFromUrl(String url)
    throws ApplicationException{
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(e);
        } catch (BadNumberException e){
            throw new ApplicationException(e);
        }
    }

如我们所见,readNumberFromUrl()方法现在仅声明引发ApplicationException。 BadUrlException和BadNumberException异常被捕获并包装在一个更通用的ApplicationException中。通过这种方式,异常包装避免了异常声明的聚集。

我个人的观点是,如果我们要做的只是包装异常而不提供任何额外的信息,那为什么还要包装它呢? try-catch块只是不执行任何操作的额外代码。将ApplicationException,BadUrlException和BadNumberException设为未检查的异常会更容易。这是上面代码的未经检查的版本:

public void readNumberFromUrl(String url){
        String data = readDataFromUrl(url);
        long number = convertData(data);
    }

如果需要,仍然可以包装未检查的异常。下面是未经检查的代码的包装版本。请注意,即使readNumberFromUrl()方法引发了ApplicationException,它也不声明引发了ApplicationException。

public void readNumberFromUrl(String url)
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        } catch (BadNumberException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        }
    }

反议2:

避免异常声明聚集到应用程序的调用堆栈中的另一种常用技术是创建基于应用程序的异常。应用程序中引发的所有异常必须是基本异常的子类。所有引发异常的方法仅需要声明即可引发基础异常。如我们所知,抛出Exception的方法也可能抛出Exception的任何子类。看起来是这样的:

public long readNumberFromUrl(String url)
    throws ApplicationException {
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }

    public class ApplicationException extends Exception{ }
    public class BadNumberException   extends ApplicationException{}
    public class BadUrlException      extends ApplicationException{}

注意如何不再声明BadNumberException和BadUrlException引发,捕获和包装。它们是ApplicationException的子类,因此它们将在调用堆栈中传播。

我的观点与异常包装相同:如果应用程序中的所有方法都声明抛出ApplicationException(基本异常),为什么不使ApplicationException处于未选中状态并保存一些try-catch块并抛出ApplicationExceptions子句呢?当方法未声明它们可能抛出的未经检查的异常时,处理它们将变得更加困难。没有声明,我们将不知道该方法可能引发哪些异常。因此,我们可能不知道如何正确处理它们。当然,如果我们有权访问代码并且可以看到该方法可能引发哪些异常,则除外。