【问题标题】:Is it safe to nest try/finally clauses like this?像这样嵌套 try/finally 子句是否安全?
【发布时间】:2013-03-06 14:22:43
【问题描述】:

由于这是一个关于 try/finally 子句行为的学术问题,因此我尝试使用一个非常通用的示例。像这样嵌套 try/finally 子句有什么危险吗?

openDatabaseConnection();
try {
    // Methods unrelated to cursor
    // ...

    String cursor_id = openCursor();
    try {
        useCursor(cursor_id);
    } finally {
        closeCursor(cursor_id);
    }

    // Methods unrelated to cursor
    // ...
} catch (Exception e) {
    genericLogError();
} finally {
    closeDatabaseConnection();
}

具体来说,我很想知道closeCursor() 是否保证在closeDatabaseConnection() 之前被调用。是否有理由嵌套一个 finally 子句之类的应该被视为不好的做法?

【问题讨论】:

  • 在同一个 finally 方法中调用两者
  • @divyabharathi:请解释你为什么推荐这个。

标签: java nested try-finally


【解决方案1】:

是的,这是有保证的。

假设在useCursor(cursor_id) 期间发生了一些异常,现在将执行 inner finally 块。 (因为异常发生在内部try 块中)。在此之后genericLogError() 将被调用(由于外部try 发生异常,其中包括内部try。只有在它之后closeDatabasConnection() 才会被执行(外部tryfinally)。这是一个可能更好地解释它的图表:

(内层try的异常→内层try的finally)这被认为是外层try的异常外层try的catch → 终于外试了。

您可以通过从块中打印来测试它。

但为什么不这样做呢?

try {
  useCursor(cursor_id);
} catch(Exception e) {
  genericLogError();
} finally {
  closeCursor(cursor_id);
  closeDatabaseConnection();
}

【讨论】:

  • 假设我将closeCursor() 放在与closeDatabaseConnection() 相同的finally 块中。如果在 try 块内但在 openCursor() 之前发生异常,这会不会导致在没有有效 cursor_id 的情况下调用 closeCursor()
  • 在这种情况下,最好将它们分成两个不同的块。如果没有有效的id,则不会调用closeCursor()
  • 我明白了。我很好奇为什么这么多 Java 开发人员鼓励我使用一个 finally 子句,同时嵌套它们可以实现相同的目标,而且如此简单......
  • 每个 try-catch 语句只能有一个 finally 子句。但是如果你有更多的try-catch,你当然可以拥有更多的finally
  • 对不起,我的意思是,我经常被敦促使用一个“大”的 try...finally 块(如您的答案)而不是使用嵌套的 try...finally 块(如我的开始示例)。
【解决方案2】:

使用 try/chatch 然后 finally。
然后你的代码就很好了。
关闭光标,总是和这里的连接是一种很好的编码习惯 您必须先致电closeCursor(cursor_id),然后再致电closeDatabaseConnection(),这正是您所做的。
由于 finally 块将始终执行,尽管在各个块的执行过程中会出现一些 Exception,所以在您的代码中,光标将首先关闭,然后数据库连接将关闭,所以这是完美的。

请找到修改后的代码 sn-p 供您参考:

 openDatabaseConnection()
    try {
        // Methods unrelated to cursor
        // ...

        String cursor_id = openCursor();
        try {
            useCursor(cursor_id);
        } 
        catch (Exception e) { //Added the catch block.
        genericLogError();
       } 
        finally {
            closeCursor(cursor_id);
        }

        // Methods unrelated to cursor
        // ...
    } catch (Exception e) {
        genericLogError();
    } finally {
        closeDatabaseConnection();
    }

或者您可以删除内部尝试并可以在外部最终本身中关闭两者,例如:

//outer try block
try{
....
}catch(Exception e){
genericLogError();
}
finally {
         closeCursor(cursor_id);
        closeDatabaseConnection();
    }

这也行。

【讨论】:

  • 为什么建议将 catch 块复制到内部 try 中?如果我没有在那里捕获异常,它不会逃到外部 try 吗?
  • 你必须在 try 块之后添加 catch。没有 catch 块的 try 块是不可能的,因为 try 块仅用于捕获异常。明白我的意思了吗?或者您可以删除内部 try 并可以在外部 finally 本身中关闭两者,例如: //outer try block try{ .... }catch(Exception e){ genericLogError(); }
  • 答案已更新。请检查一次。如果有任何问题,请告诉我。
  • 但是 try 块不仅用于捕获异常,它们还用于清理代码,如我的示例所示。没有 catch 子句的 try...finally 结构在语法上是有效的,编译和执行都很好。
  • 如果您无法在该级别执行任何操作(例如重试操作或将问题传达给用户),则不应捕获异常。大多数情况下,您希望异常冒泡到适当的层,这意味着没有 catch 的 try-finally 应该比 try-catch 更常见。
【解决方案3】:

是的,closeCursor() 保证在 closeDatabaseConnection() 之前被调用,在这种情况下。

您可以将两个调用放在一个 finally 块中(将对 closeCursor() 的调用移动到调用 finally 的同一块 closeDatabaseConnection()),但这需要在外部范围中声明游标,这可能不可取。例如,您可能希望将所有使用光标的代码放入单独的方法中;在这种情况下,您可能希望在这个新方法中使用 finally 块来关闭光标。

一般来说,我不认为嵌套finally 子句应该被认为是一种不好的做法。人们应该考虑代码的一般可读性,例如一种方法中的行数 - 也许将方法分成两部分会更好,在这种情况下,会有两个 finally 块,每个方法中一个。或者,如果您的代码足够简单且足够短,可以采用一种方法,您可以重新组织代码以仅包含一个 finally 块。

【讨论】:

    【解决方案4】:

    当控制离开 try 块时,finally 子句的执行得到保证。这里控件将内部try块放在外部块之前,所以执行顺序是有保证的。

    没有理由认为这应该被认为是不好的做法,除了你正在制作一个可以分成两个或更多的大方法。

    【讨论】:

      【解决方案5】:

      这并没有错,假设您首先需要像这样嵌套 try-catch 块。 Maroun's 答案对于您提供的示例会更好。如果您在“光标”清理中涉及大量局部变量,则您建议的方法更适合的一个示例可能是:

      openDatabaseConnection();
      try {
          Cursor cursor = openCursor();
          CursorHelper helper = new CursorHelper(cursor);
          String cursor_id = cursor.getId();
          String something_else = cursor.getSomethingElse();
          try {
              useCursor(cursor_id, something_else, helper);
          } finally {
              closeCursor(cursor_id, something_else, helper);
          }
      } catch (Exception e) {
          genericLogError();
      } finally {
          closeDatabaseConnection();
      }
      

      父try块会捕获嵌套的异常抛出,但是嵌套的finally块会先被调用。这将游标变量的范围保持在第一个 try/catch 块中。

      如果您想将所有这些代码放入一个 try/catch 块中,则必须在该块之外声明一些变量,这会开始影响可读性:

      openDatabaseConnection();
      CursorHelper helper = null;
      String cursor_id = null;
      String something_else = null;
      try {
          Cursor cursor = openCursor();
          helper = new CursorHelper(cursor);
          cursor_id = cursor.getId();
          something_else = cursor.getSomethingElse();
          useCursor(cursor_id, something_else, helper);
      } catch (Exception e) {
          genericLogError();
      } finally {
          if (cursor_id != null) {
              closeCursor(cursor_id, something_else, helper);
          }
          closeDatabaseConnection();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-08
        • 1970-01-01
        相关资源
        最近更新 更多