5

我有一个关于在 Java 中引发异常的问题,这似乎是我的一种误解,我想为自己澄清一下。

我一直在阅读处理异常代码的两种基本方法是:

1.) 使用“throw new ...”在 try-block 中抛出异常,并立即在 catch-block 中捕获它——所谓的 try-throw-catch 机制。

2.) 用“throw new ...”在方法中抛出异常,然后在方法的标头中声明该方法可能会用“throws ...”抛出异常 - 即所谓的 pass-the-buck。

我最近读到“抛出异常然后用相同的方法捕获它没有任何意义”,这让我思考是否我理解错误的方式,或者写这个的人有什么东西其他的。第一种处理异常的方法不正是这样做的吗(try-throw-catch 机制)?我的意思是,它抛出一个异常并以相同的方法捕获它。我已经读过,在一种方法中抛出异常并在另一种方法中捕获它是一种更好的做法,但这只是一种(可能更好)的方法。另一种方式也是合法和正确的,不是吗?

请您对此发表评论好吗?非常感谢。

4

11 回答 11

21

当该方法无法自行解决异常时,应该从该方法中抛出异常。

例如,aFileNotFoundException被抛出是因为 FileInputStream 本身无法处理文件丢失的情况;需要抛出该异常,以便最终用户应用程序可以处理该问题。new FileInputStream(new File(filename))

在某些情况下,可以在方法中处理异常。例如,BadLocationException可以在足够智能的方法中处理抛出 a 的文档模型方法。根据问题,可以处理或重新抛出异常。

(无论如何,我认为从 try-catch 块中抛出异常以便可以执行 catch 块代表非常糟糕的逻辑流程)

于 2009-02-09T14:58:28.543 回答
6

我想你误解了第一个案例。通常,当您调用某些可能引发异常的方法时,您会添加一个 try-catch-block。捕捉本地抛出的异常确实没有多大意义。特别是您不应该使用异常退出循环,因为与标准方法相比,这非常慢。

于 2009-02-09T14:55:27.877 回答
4

第一种处理异常的方法不正是这样做的吗(try-throw-catch 机制)?我的意思是,它抛出一个异常并以相同的方法捕获它。

这不是“处理异常的方式”——这完全是胡说八道。异常的全部意义在于让调用堆栈上的另一个方法处理它。如果您要在同一个方法中处理条件,那么使用异常是没有意义的——这就是 if() 的用途!如果这使您的方法的控制流过于复杂,您可能应该将一些逻辑重构为单独的方法 - 然后让其余方法主体捕获的那些抛出异常可能是有意义的。

话虽如此,我可以想象一种特殊情况,在同一个方法中抛出和捕获异常是有意义的:当你已经调用了一个可能抛出异常并有一个 catch 块来处理它的方法时,然后在在某些情况下,抛出异常以指示现有 catch 块可以以相同方式处理的类似问题可能是有意义的。

于 2009-02-09T15:11:54.733 回答
3

我将依次回答您的问题,然后在末尾添加一些评论。我不是异常处理的权威,但我希望我的评论对您有所帮助。


“不是第一种处理异常的方法就是这样做的”?

我的回答是肯定的,正如您所描述的,第一种方法确实通过在同一方法中抛出和捕获异常来操作。但是,我不知道 try-throw-catch 必须像您描述的那样起作用。


“我已经读过,在一种方法中抛出异常并在另一种方法中捕获它是一种更好的做法,但这只是一种(可能更好)方式。另一种方式也是合法且正确的,不是吗? "

我同意从第二种方法捕获异常更好,但第一种方法是合法的。这是对的吗?好吧,这由您决定,毕竟这是您的代码。

在大多数情况下,我同意抛出异常然后立即在同一方法中捕获该异常是没有意义的。如果我们这样做是因为该方法特别长/复杂,并且使用其他逻辑处理错误会使事情变得更加复杂,那么我建议将其中一些逻辑移至另一个方法并调用该方法并捕获其异常。

如果我们的代码更简单,那么使用不包含抛出异常的代码来处理错误可能会很容易。


我的评论:

你提到的try-throw-catch机制可能不需要在同一个方法中抛出异常。我必须阅读您认为可以确定的文本,但我认为没有必要。如果它不需要在同一个方法中抛出异常,那么你的异常处理策略是 1) 和 2) 的组合。

在组合中,一种方法将使用 try-throw-catch 机制来捕获被调用方法抛出的异常。在我看来,1) 和 2) 应该一起工作以形成您的异常处理策略。

现在,也许有人会过来给我们一些很好的理由,说明我们可能想在同一个方法中抛出异常。我希望有一些,但对我来说,它们似乎是例外,而不是规则。

干杯,埃德

于 2009-02-09T15:32:07.260 回答
3

写“抛出异常然后用相同的方法捕获它没有任何意义”的人有权发表他们的意见,但它没有被广泛分享。有很多情况需要在同一方法中抛出和捕获异常。最简单的是您正在执行一系列操作,其中任何一个操作失败都会使其余操作无效。如果您检测到这些操作之一失败,那么抛出异常并在方法结束时捕获它是完全合理的。事实上,这是合乎逻辑的做事方式。可以说你可以重写代码以不使用异常,也许有一些状态标志和一个或两个 break 语句,但你为什么要这样做?使用异常可以清楚地知道发生了什么并提高代码的可读性。

于 2009-02-09T15:16:17.813 回答
2

对于第一种方式,您的意思是这样的:

try {
  ok = doSomething();
  if (!ok) {
   throw new Exception("Error");
  }
 ok = doSomethingElse();
}catch (Exception e) {
}

这将允许您退出 try-catch 块而不执行其余部分。这是我能想到的使用 throw 抛出异常并自己在 try-catch 块中捕获它的唯一有效用法。但是,应该使用标准 if 块。我不明白为什么有人应该抛出异常然后自己捕获它。

第二种方式更标准,特别是如果抛出异常的方法的调用者是外部模块。这是一种表明发生了真正错误的信号的方式。处理异常是调用者的责任。

于 2009-02-09T14:56:05.973 回答
2

如果您要手动抛出异常,那么显然您知道有一些错误需要处理。与其抛出新的异常,然后捕获它,然后立即处理错误,为什么不直接处理错误呢?您(和处理器)不需要完成生成异常并捕获它的所有工作。该异常还使代码更难阅读和调试。

如果出现以下情况,您将抛出异常,而不是立即处理错误:

  • 其他代码,例如调用您的方法的代码,应该处理该错误。例如,如果您的代码不是 UI 代码,那么它可能不应该生成窗口。这是你的方法#2。

  • 您可以利用 try、catch 和 finally 块。您可以通过这种方式编写更清晰的代码,但我认为 90% 的情况下,您的代码使用简单的 if 语句会更具可读性。

于 2009-02-09T15:08:03.793 回答
1

Joshua Bloch 在Effective Java, 2nd Edition的 Item 57中专门处理了使用控制流的异常:

第 57 条:仅在例外情况下使用例外

...例外,顾名思义,仅用于例外情况;它们不应该用于普通的控制流。[斜体我的]

因此,虽然使用异常来控制流程肯定“有效”,但不建议这样做。

于 2009-02-09T16:48:24.410 回答
1

我的经验是,使用第一种方法会使您的代码很快变得不可读-因为功能和错误处理混淆了。但在某些情况下,您有 try{}catch{} finaly {} 是有意义的 - 例如在您总是希望关闭连接的文件处理或数据库处理中。

try{ //do something
}catch(Exception ex){
//log
}finally{
//connection.close
}

对于其他一切,我使用第二个选项 - 只是为了集中我的错误处理例程并保持实现业务逻辑本身的代码的可读性。

于 2009-02-09T14:56:24.053 回答
1

在我看来,您编写的 try 块不应包含任何在同一方法中捕获的“新抛出”。当您抛出异常时,您是在说“我遇到了我无法处理的情况;其他人将不得不处理它。” 带有“throw new”的方法应该创建一个未检查的异常来抛出或在其方法签名中声明一个检查的异常。

如果您正在使用可能引发异常的 3rd 方类,那么如果发生异常时您实际上可以处理这种情况,那么您的方法应该有一个 try/catch 块。否则,您应该推迟到另一个可以的课程。

我不会创建自己的异常,然后用相同的方法捕获它。

于 2009-02-09T14:58:33.223 回答
0

这看起来是无稽之谈(在同一方法中抛出和捕获)的原因是因为这将是使用异常进行流控制的场景。如果您已经有足够的数据来识别应该引发异常的条件,那么您可以使用该信息来使用条件。

见下文:

1)在同一方法中抛出和捕获异常(错误

public void method() { 
    try {    
        workA...
        workA...
        workA...
        workA...    
        if( conditionIvalid() && notEnoughWhatever()  && isWrongMoment() ) { 
            throw new IllegalStateException("No the rigth time" );
        }
        workB...
        workB...
        workB...
        workB...
    } catch( IllegalStateException iee ) { 
        System.out.println( "Skiped workB...");
    }
    workC....
    workC....
    workC....
    workC....
}

在这种情况下,异常抛出用于跳过“workB”部分。

这样做会更好:

2) 使用条件控制流(

public void method() { 
    workA...
    workA...
    workA...
    workA...    
    if( !(conditionIvalid() && notEnoughWhatever()  && isWrongMoment() ) ) { 
        //throw new IllegalStateException("No the rigth time" );
        workB...
        workB...
        workB...
        workB...

    }
    workC....
    workC....
    workC....
    workC....
}

然后你可以重构条件:

    if( !(conditionIvalid() && notEnoughWhatever()  && isWrongMoment() ) ) { 

为了

    if( canProceedWithWorkB() ) {

实现为:

  boolean canProceedWithWorkB() {  
      return !(conditionIvalid() && notEnoughWhatever()  && isWrongMoment() );
  } 
于 2009-02-09T16:31:13.077 回答