何时选择已检查和未检查的例外

时间:2008-08-26 08:45:56

标签: java exception checked-exceptions

在Java(或任何其他具有已检查异常的语言)中,在创建自己的异常类时,如何确定是应该选中还是取消选中它?

我的直觉是说,如果调用者可以以某种富有成效的方式恢复,那么将调用一个已检查的异常,因为未经检查的异常对于不可恢复的情况会更多,但我会感兴趣其他人的想法。

18 个答案:

答案 0 :(得分:206)

已检查的例外情况很好,只要您了解何时应该使用它们。 Java核心API无法遵循SQLException的这些规则(有时候对于IOException),这就是它们如此糟糕的原因。

已检查例外应用于可预测,但不可疑错误恢复。< / p>

未选中的例外应该用于其他所有内容。

我会为你解决这个问题,因为大多数人都误解了这意味着什么。

  1. 可预测但不可预防:调用者尽其所能来验证输入参数,但是他们控制之外的某些条件导致操作失败。例如,您尝试读取文件,但有人在您检查文件是否存在以及读取操作开始的时间之间删除它。通过声明已检查的异常,您告诉调用者预测此失败。
  2. 合理恢复:没有必要告诉呼叫者预测他们无法恢复的异常。如果用户尝试从不存在的文件中读取,则调用者可以提示他们输入新文件名。另一方面,如果方法由于编程错误(无效的方法参数或错误的方法实现)而失败,那么应用程序无法在执行中期解决问题。它能做的最好的就是记录问题并等待开发人员稍后修复它。
  3. 除非您投掷的例外符合上述条件的全部,否则应使用未经检查的例外。

    在每个级别重新评估:有时捕获已检查异常的方法不是处理错误的正确位置。在这种情况下,请考虑对您自己的呼叫者来说什么是合理的。如果异常是可预测的,不可避免且合理的,他们可以从那时恢复,你应该自己抛出一个检查异常。如果不是,则应将异常包装在未经检查的异常中。如果您遵循此规则,您会发现自己将已检查的例外转换为未经检查的例外情况,反之亦然,具体取决于您所在的图层。

    对于已检查和未检查的例外,使用正确的抽象级别。例如,具有两个不同实现(数据库和文件系统)的代码存储库应避免通过抛出SQLExceptionIOException来公开特定于实现的详细信息。相反,它应该将异常包装在跨越所有实现的抽象中(例如RepositoryException)。

答案 1 :(得分:55)

来自A Java Learner

  

发生异常时,您必须这样做   要么抓住并处理异常,   或告诉编译器你无法处理   它通过声明你的方法   抛出那个异常,然后是代码   使用你的方法将不得不   处理该异常(即使它也   可以选择宣布它抛出   如果它无法处理它的例外)。

     

编译器将检查我们是否已完成   两件事之一(赶上,或   宣布)。所以这些被称为Checked   例外。但是错误和运行时   不检查例外情况   编译器(即使你可以选择   抓住或宣布它不是   需要)。所以,这两个被称为   未经检查的例外。

     

错误用于表示这些错误   在外面发生的条件   应用程序,如崩溃   系统。运行时异常是   通常由于故障而发生   应用逻辑。你做不到   这些情况下的任何事情什么时候   发生运行时异常,你必须   重写你的程序代码。所以,这些   不会被编译器检查。这些   运行时异常将在中发现   开发和测试期。然后   我们必须重构我们的代码才能删除   这些错误。

答案 2 :(得分:46)

  

我使用的规则是:永远不要使用未经检查的例外! (或当你看不到任何方法时)

相反的情况非常强烈:绝不使用已检查的异常。我不愿在辩论中偏袒任何一方,但似乎有一个广泛的共识,即在事后看来,引入经过检查的例外是一个错误的决定。请不要拍摄信使并参考those arguments

答案 3 :(得分:42)

在任何足够大的系统上,有很多层,检查异常都没用,因为无论如何,你需要一个架构级策略来处理如何处理异常(使用故障屏障)

使用已检查的异常,您的错误处理状态是微管理的,并且在任何大型系统上都无法忍受。

大多数情况下,您不知道错误是否“可恢复”,因为您不知道API的调用者位于哪个层中。

假设我创建了一个StringToInt API,它将整数的字符串表示形式转换为Int。如果使用“foo”字符串调用API,我必须抛出一个已检查的异常吗?它可以恢复吗?我不知道,因为在他的图层中,我的StringToInt API的调用者可能已经验证了输入,如果抛出此异常,则可能是错误或数据损坏,并且该层无法恢复。

在这种情况下,API的调用者不想捕获异常。他只想让例外“冒泡”。如果我选择了一个已检查的异常,那么这个调用者将只有很多无用的catch块才能人为地重新抛出异常。

什么是可恢复的,大部分时间取决于API的调用者,而不是API的写入者。 API不应使用已检查的异常,因为只有未经检查的异常允许选择捕获或忽略异常。

答案 4 :(得分:28)

你是对的。

未经检查的例外用于让系统fail fast成为一件好事。您应该清楚地说明您的方法是什么,以便正常工作。这样,您只能验证输入一次。

例如:

/**
 * @params operation - The operation to execute.
 * @throws IllegalArgumentException if the operation is "exit"
 */
 public final void execute( String operation ) {
     if( "exit".equals(operation)){
          throw new IllegalArgumentException("I told you not to...");
     }
     this.operation = operation; 
     .....  
 }
 private void secretCode(){
      // we perform the operation.
      // at this point the opreation was validated already.
      // so we don't worry that operation is "exit"
      .....  
 }

举一个例子。关键是,如果系统快速失败,那么你就会知道它失败的地点和原因。您将获得类似的堆栈跟踪:

 IllegalArgumentException: I told you not to use "exit" 
 at some.package.AClass.execute(Aclass.java:5)
 at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
 ar ......

你会知道发生了什么。 “delegateTheWork”方法中的OtherClass(在第4569行)使用“exit”值调用您的类,即使它不应该等等。

否则,您必须在代码中撒上验证,这很容易出错。此外,有时很难追踪出现问题的原因,您可能会遇到数小时令人沮丧的调试

NullPointerExceptions也会发生同样的事情。如果你有一个包含大约15个方法的700行类,它们使用30个属性,并且它们都不能为null,而不是在每个这些方法中验证可空性,你可以将所有这些属性设置为只读并在构造函数中验证它们或工厂方法。

 public static MyClass createInstane( Object data1, Object data2 /* etc */ ){ 
      if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }

  }


  // the rest of the methods don't validate data1 anymore.
  public void method1(){ // don't worry, nothing is null 
      ....
  }
  public void method2(){ // don't worry, nothing is null 
      ....
  }
  public void method3(){ // don't worry, nothing is null 
      ....
  }

检查异常当程序员(您或您的同事)做的一切正确,验证输入,运行测试以及所有代码都很完美,但代码连接到第三个可能已关闭的派对Web服务(或您正在使用的文件已被其他外部进程删除等)。 Web服务甚至可以在尝试连接之前进行验证,但在数据传输期间出现问题。

在这种情况下,您或您的同事无法帮助您。但是你仍然需要做一些事情而不是让应用程序死掉并消失在用户眼中。您使用已检查的异常并处理异常,当发生这种情况时您能做什么?大多数情况下,只是为了尝试记录错误,可能会保存您的工作(应用程序工作)并向用户显示一条消息。 (网站blabla已关闭,请稍后重试等)

如果检查过的异常被过度使用(通过在所有方法签名中添加“throw Exception”),那么你的代码将变得非常脆弱,因为每个人都会忽略该异常(因为它太笼统)和代码的质量会受到严重损害。

如果过度使用未经检查的异常,则会发生类似情况。该代码的用户不知道是否会出现问题,会出现很多try {...} catch(Throwable t)。

答案 5 :(得分:19)

这是我的“最终经验法则” 我用:

    我的方法代码中的
  • 未经检查的异常因<来电者而导致失败<(涉及 explicit and complete documentation )< / LI>
  • 已检查异常,因为callee 导致失败,我需要向任何想要使用我的代码的人明确说明

与之前的答案相比,这是一个明确的理由(人们可以同意或不同意)使用一种或另一种(或两种)例外。


对于这两个异常,我将为我的应用程序创建自己的未经检查和检查的异常(一个好的做法,as mentionned here),除了非常常见的未经检查的异常(如NullPointerException)

因此,例如,下面这个特定函数的目标是使(或得到,如果已经存在)一个对象,
意思是:

  • 必须存在的对象的容器(CALLER的责任) =&GT;未经检查的异常,并清除此调用函数的javadoc注释)
  • 其他参数不能为空 (选择将编码器放在CALLER上的编码器:编码器不会检查空参数,但编码器是否有文件)
  • 结果不能为空 (责任和被调用者代码的选择,对呼叫者来说非常感兴趣的选择) =&GT;检查异常,因为如果无法创建/找到对象,每个调用者必须做出决定,并且必须在编译时强制执行该决定:他们不能使用此函数而不必处理这种可能性,这意味着使用此检查例外)。

示例:


/**
 * Build a folder. <br />
 * Folder located under a Parent Folder (either RootFolder or an existing Folder)
 * @param aFolderName name of folder
 * @param aPVob project vob containing folder (MUST NOT BE NULL)
 * @param aParent parent folder containing folder 
 *        (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
 * @param aComment comment for folder (MUST NOT BE NULL)
 * @return a new folder or an existing one
 * @throws CCException if any problems occurs during folder creation
 * @throws AssertionFailedException if aParent is not in the same PVob
 * @throws NullPointerException if aPVob or aParent or aComment is null
 */
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
    final IPVob aPVob, final Comment aComment) throws CCException {
    Folder aFolderRes = null;
    if (aPVob.equals(aParent.getPVob() == false) { 
       // UNCHECKED EXCEPTION because the caller failed to live up
       // to the documented entry criteria for this function
       Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }

    final String ctcmd = "mkfolder " + aComment.getCommentOption() + 
        " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);

    final Status st = getCleartool().executeCmd(ctcmd);

    if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
        aFolderRes = Folder.getFolder(aFolderName, aPVob);
    }
    else {
        // CHECKED EXCEPTION because the callee failed to respect his contract
        throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
    }
    return aFolderRes;
}

答案 6 :(得分:19)

这不仅仅是从异常中恢复的能力问题。在我看来,最重要的是调用者是否有兴趣捕获异常。

如果您编写要在其他地方使用的库或应用程序中的低级层,请问自己调用者是否有兴趣捕获(了解)您的异常。如果不是,那么使用未经检查的例外,这样就不会给他带来不必要的负担。

这是许多框架使用的哲学。特别是,我想到了Spring和hibernate - 它们将已知的已检查异常转换为未经检查的异常,因为在Java中过度使用了已检查的异常。我能想到的一个例子是来自json.org的JSONException,它是一个经过检查的异常,并且非常讨厌 - 它应该是未经检查的,但是开发人员根本没有考虑过它。

顺便说一句,大多数情况下,调用者对异常的兴趣与从异常中恢复的能力直接相关,但情况并非总是如此。

答案 7 :(得分:13)

这是一个非常简单的解决方案,可以解决您的Checked / Unchecked困境。

规则1:在代码执行之前将未经检查的异常视为可测试条件。 例如......

x.doSomething(); // the code throws a NullPointerException

其中x为null ... ......代码应该有以下内容......

if (x==null)
{
    //do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException.
    x = new X();
}
x.doSomething();

规则2:将Checked Exception视为代码执行时可能出现的不可测试条件。

Socket s = new Socket(“google.com”, 80);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();

...在上面的示例中,由于DNS服务器关闭,URL(google.com)可能无法使用。即使在DNS服务器正在工作并且将“google.com”名称解析为IP地址的情况下,如果连接到google.com,则在任何时候后续,网络可能会关闭。在读取和写入流之前,您根本无法一直测试网络。

在我们知道是否存在问题之前,有时候代码必须执行。通过强制开发人员以强制他们通过Checked Exception处理这些情况的方式编写代码,我不得不向发明这个概念的Java创建者倾斜。

通常,Java中的几乎所有API都遵循上述2条规则。如果您尝试写入文件,则磁盘可能会在完成写入之前填满。其他进程可能导致磁盘已满。没有办法测试这种情况。对于那些随时与硬件交互的人来说,使用硬件可能会失败,Checked Exceptions似乎是解决这个问题的优雅解决方案。

此处有一个灰色区域。如果需要进行许多测试(一个令人兴奋的if语句带有很多&amp;&amp;和||),抛出的异常将是一个CheckedException,因为要做到这一点太痛苦了 - 你根本就不能说这个问题是编程错误。如果少于10个测试(例如'if(x == null)'),那么程序员错误应该是UncheckedException。

在处理语言翻译时,事情变得有趣。根据上述规则,语法错误是否应被视为已检查或未检查的异常?我认为如果语言的语法可以在执行之前进行测试,那么它应该是一个UncheckedException。如果无法测试语言 - 类似于汇编代码在个人计算机上运行的方式,那么语法错误应该是一个Checked Exception。

上述2条规则可能会删除您可以选择的90%的关注点。要总结规则,请遵循以下模式...... 1)如果要执行的代码在执行之前可以进行测试以使其正确运行,并且如果发生异常 - 例如程序员错误,则异常应该是UncheckedException(RuntimeException的子类)。 2)如果要执行的代码在执行之前无法进行测试以使其正确运行,则Exception应该是Checked Exception(Exception的子类)。

答案 8 :(得分:8)

您可以将其称为已选中或未经检查的例外;但是,程序员可以捕获两种类型的异常,因此最好的答案是:将所有的异常写为未选中并记录它们。这样,使用您的API的开发人员可以选择是否要捕获该异常并执行某些操作。检查异常完全浪费了每个人的时间,这使得您的代码成为令人震惊的噩梦。然后,适当的单元测试将提出您可能需要捕获的任何异常并执行某些操作。

答案 9 :(得分:7)

已检查例外:    如果客户端可以从异常中恢复并希望继续,请使用已检查的异常。

未选中的例外:    如果客户端在异常后无法执行任何操作,则会引发未经检查的异常。

示例:如果您希望在方法A()中进行算术运算并基于A()的输出,则必须执行另一个操作。如果输出在方法A()中为null,而在运行时期间您没有预料到,那么您应该抛出Null指针Exception,即运行时异常。

参考here

答案 10 :(得分:2)

我希望在多年的开发经验之后分享我的观点:

  1. 检查异常。这是业务用例或调用流程的一部分,这是我们期望或不期望的应用程序逻辑的一部分。例如连接被拒绝,条件不满足等。我们需要处理它并向用户显示相应的消息,说明发生了什么以及下一步做什么(稍后再试)等。 我通常将其称为后处理异常或“用户”异常。

  2. 未经检查的例外情况。这是编程异常的一部分,软件代码编程中的一些错误(错误,缺陷),反映了程序员必须按照文档使用API​​的方式。如果外部lib / framework文档说它希望获得某个范围内的数据而非null,因为将抛出NPE或IllegalArgumentException,程序员应该期望它并按照文档正确使用API​​。否则将抛出异常。 我通常将其称为预处理异常或“验证”异常。

  3. 目标受众。现在让我们谈谈目标受众或一组例外的人(根据我的观点):

    1. 检查异常。目标受众是用户/客户。
    2. 未经检查的例外情况。目标受众是开发人员。换句话说,未经检查的异常仅供开发人员使用。
    3. 按应用程序开发生命周期阶段。

      1. 检查异常被设计为在整个生产生命周期中存在,作为应用程序处理异常情况的正常和预期机制。
      2. 未经检查的异常仅在应用程序开发/测试生命周期中存在,所有这些异常应在此期间修复,并且在应用程序运行时不应抛出。
      3. 框架通常使用未经检查的异常(例如Spring)的原因是框架无法确定应用程序的业务逻辑,这取决于开发人员捕获并设计自己的逻辑。

答案 11 :(得分:2)

我同意对未经检查的异常的偏好,特别是在设计API时。调用者总是可以选择捕获记录的,未经检查的异常。你不是不必要地强迫来电者。

我发现在较低级别使用的已检查异常作为实现细节。通常看起来比控制指定错误“返回代码”更好的控制机制流程。它有时可以帮助看到一个想法对低级别代码更改的影响...在下游声明一个已检查的异常并查看谁需要调整。如果有很多泛型,则最后一点不适用: catch(异常e)抛出异常,这通常不会过于深思熟虑。

答案 12 :(得分:1)

我们必须根据是否是程序员错误来区分这两种类型的异常。

  • 如果错误是程序员错误,则必须是未经检查的异常例如: SQLException / IOException / NullPointerException。这些例外是 编程错误。它们应由程序员处理。而在 JDBC API,在Spring JDBCTemplate中,SQLException是Checked Exception 这是一个未经检查的异常。程序员不必担心 使用Spring时发生SqlException。
  • 如果错误不是程序员错误,并且原因是由外部引起的,则必须是Checked Exception。 。例如: 文件被删除或文件许可权被他人更改,它 应该被恢复。

FileNotFoundException是了解细微差别的好例子。如果找不到文件,则抛出FileNotFoundException。此异常有两个原因。如果文件路径是由开发人员定义的,或者是通过GUI从最终用户那里获取的,则应为Unchecked Exception。如果文件被其他人删除,则应为“已检查异常”。

检查异常可以通过两种方式处理。这些正在使用try-catch或传播异常。在传播异常的情况下,由于处理异常,调用堆栈中的所有方法将紧密耦合。因此,我们必须谨慎使用Checked Exception。

如果要开发分层的企业系统,则必须选择要抛出的大部分未经检查的异常,但是如果您无能为力,请不要忘记使用经过检查的异常。

答案 13 :(得分:1)

我认为我们可以从几个问题中考虑一些问题:

为什么会出现这种情况?发生时我们能做什么

错误地调用了一个bug。,例如null对象的方法。

String name = null;
... // some logics
System.out.print(name.length()); // name is still null here

这种例外应该在测试期间修复。否则,它会破坏生产,你得到一个非常高的bug,需要立即修复。不需要检查这种异常。

来自外部的输入,您无法控制或信任外部服务的输出。

String name = ExternalService.getName(); // return null
System.out.print(name.length());    // name is null here

在这里,如果要在null时继续,可能需要检查名称是否为空,否则,您可以单独使用它,它将在此处停止并为调用者提供运行时异常。 不需要检查这种例外情况。

来自外部的运行时异常,您无法控制或信任外部服务。

在这里,如果要在发生事件时继续,可能需要捕获来自ExternalService的所有异常,否则,您可以单独使用它,它将在此处停止并为调用者提供运行时异常。

通过外部检查异常,您无法控制或信任外部服务。

在这里,如果要在发生事件时继续,可能需要捕获来自ExternalService的所有异常,否则,您可以单独使用它,它将在此处停止并为调用者提供运行时异常。

在这种情况下,我们是否需要知道ExternalService中发生了什么类型的异常?取决于:

  1. 如果您可以处理某些异常,则需要捕获它们并进行处理。对其他人来说,泡他们。

  2. 如果您需要记录或响应用户特定的execption,您可以捕获它们。对其他人来说,泡他们。

答案 14 :(得分:1)

每当异常不太可能发生时,我们甚至可以在捕获之后继续进行,并且我们无法做任何事情来避免该异常,那么我们就可以使用已检查的异常。

每当我们想要在发生特定异常时做一些有意义的事情,并且当预期异常但不确定时,我们就可以使用已检查的异常。

每当异常在不同的层中导航时,我们不需要在每个层中捕获它,在这种情况下,我们可以使用运行时异常或将异常包装为未经检查的异常。

当最有可能发生的异常时,使用运行时异常,没有办法进一步发展,没有任何东西可以恢复。因此,在这种情况下,我们可以针对该例外采取预防措施。 EX:NUllPointerException,ArrayOutofBoundsException。这些更有可能发生。在这种情况下,我们可以在编码时采取预防措施以避免此类异常。否则我们将不得不在每个地方编写try catch块。

可以进行更多常规例外未选中,不常检查。

答案 15 :(得分:1)

对于您希望向调用者提供信息的可恢复情况(即权限不足,文件未找到等),已检查的异常非常有用。

如果有的话,很少使用未经检查的异常,以便在运行时通知用户或程序员严重错误或意外情况。如果您正在编写将由其他人使用的代码或库,请不要抛弃它们,因为它们可能不会期望您的软件抛出未经检查的异常,因为编译器不会强制它们被捕获或声明。

答案 16 :(得分:0)

我认为在声明Application Exception时,它应该是Unchecked Exception,即RuntimeException的子类。 原因是它不会在方法上使用try-catch和throws声明来混淆应用程序代码。如果您的应用程序使用的是Java Api,则会抛出必须处理的已检查异常。对于其他情况,应用程序可以抛出未经检查的异常。如果应用程序调用者仍需要处理未经检查的异常,则可以完成。

答案 17 :(得分:-12)

我使用的规则是:永远不要使用未经检查的例外! (或当你看不到任何方法时)

从使用您的库的开发人员或使用您的库/应用程序的最终用户的角度来看,真的很难面对因未知异常导致崩溃的应用程序。依靠抓住机会也不是好事。

这样最终用户仍然可以看到错误消息,而不是应用程序完全消失。