【问题标题】:Do you really need the 'finally' block你真的需要'finally'块吗
【发布时间】:2011-04-21 03:56:10
【问题描述】:

在 java 中有 3 种 try...catch...finally 块的排列。

  1. 尝试...抓住
  2. 尝试...抓住...终于
  3. 尝试...终于

一旦 finally 块被执行,控制就转到 finally 块之后的下一行。如果我删除 finally 块并将其所有语句移到 try...catch 块之后的行,这是否与将它们放在 finally 块中的效果相同?

【问题讨论】:

标签: java exception-handling


【解决方案1】:

我知道这是一个非常古老的问题,但我今天遇到了,我对给出的答案感到困惑。我的意思是,当这个问题有一个非常直截了当的实际答案时,它们都是正确的,但都是理论上甚至哲学层面的答案。

如果你在 catch 块(甚至是 try 块)中放置了 return、break、continue 或任何其他改变代码顺序执行的 java 关键字,那么 finally 中的语句块仍将被执行。

例如:

public void myFunc() {

    double p = 1.0D;
    String str = "bla";
    try{
        p = Double.valueOf(str);
    }
    catch(Exception ex){
        System.out.println("Exception Happened");
        return;  //return statement here!!!
    }finally{
        System.out.println("Finally");
    }
    System.out.println("After finally");
}

执行此代码时将打印:

Exception Happened 
Finally

这是 finally 块存在的最重要原因。大多数答案都暗示它或在场边提及它,但没有一个人强调它。我认为因为这是一个新手问题,所以一个直截了当的答案非常重要。

【讨论】:

  • 谢谢,这就是我要找的答案。
  • 我们需要更多像你这样的工程师。
【解决方案2】:

我认为willcode最接近表达这里的关键点,可能每个人都这么说但不清楚。

问题是您所问的问题确实存在很大问题:“如果我在 catch 块之后编写所有语句而不是将它们写入 finally 块,那么会有什么问题吗?”

如果你把所有语句写在 catch 块之后,你的意思是

1) 你总会捕捉到异常。

2) 捕获异常后,您将始终继续执行下一条语句。

这意味着您将始终“正常”地在出现异常后继续执行,这通常是您从不实际上想要做的事情。

例外应该就是这样 - 例外。如果您实际上可以处理异常,那么编写代码以首先考虑这些条件并且根本不导致异常总是更好。如果您遵循此模型,那么异常就是真正的异常——您无法预料或最多无法修复的情况。真的没有预料到是你应该努力的方向。 这意味着通常你无法处理真正的异常,这也意味着你不应该只是继续执行,而是经常结束应用程序。

通常的做法是允许错误传播回调用堆栈。有人说这是在链条中更高的人可能能够处理它的情况下完成的。我想说这基本上永远不会发生,这样做有两个真正的目的。一个可能是用户可以修复的东西,如果有的话。因此,您将错误传播回来,直到您到达可以将其报告给用户的位置。或者二,用户无法修复它,但您想获取整个调用堆栈以进行调试。然后你抓住它在顶部优雅地失败。

finally 块现在应该对你有更多的意义。正如每个人所说,它总是运行。 finally 最清晰的用法实际上是在 try... finally 块中。您现在所说的是,如果代码运行良好,那就太好了。我们仍然需要做一些清理,最后总是执行,然后我们继续。但是如果发生异常,我们现在真的需要 finally 块,因为我们可能仍然需要做一些清理工作,但是我们不再在这里捕获异常,所以我们将不再继续前进。 finally 块对于确保进行清理至关重要。

除非有一定的经验,否则总是会停止执行的异常的想法对于某人来说可能很难理解,但实际上这就是始终做事的方式。如果发生了错误,要么它太小了,你应该从一开始就考虑到它,或者有越来越多的错误等待发生。

“吞下”错误 - 捕获它们并继续前进是您能做的最糟糕的事情,因为您的程序变得不可预测,您无法找到并修复错误。

编写良好的代码将包含尽可能多的 try ... finally 块,以确保无论结果如何总是释放资源。但是编写良好的代码通常只包含少量的 try ... catch 块,这些块的存在主要是为了让应用程序尽可能优雅地失败,或者服从用户,这意味着至少总是将消息传递给用户等。但你通常不会只是捕捉到错误并继续前进。

【讨论】:

  • 您的结论(1 和 2)是绝对错误的 - 不,这个问题并不意味着他将始终捕获异常和/或他将始终继续执行后续指令。 1)即使你没有捕捉到异常,你仍然继续执行后续行,除非你从函数返回; 2)如果您不返回,无论您继续尝试阻止还是捕获 - 您总是会继续执行后续指令(如果有的话),这是正确的,这里没有任何问题。我会回答,一个人可以在尝试期间返回 - 但最终仍然会被执行。这就是区别。
  • @GiorgiTsiklauri 您说执行将继续“即使您没有捕获异常”,但这并不总是正确的-您的意思是根本没有发生异常吗?如果是这样,您没有考虑何时有异常,但它不是您有 catch 语句的异常。最后会很好地解决这个问题。 “尝试中返回”绝对不是最终存在的唯一原因。
【解决方案3】:

重点是finally 块保证即使引发异常而不是捕获也会被执行。然后,您可以一次性使用finally 块来执行必要的清理,例如关闭流。 finally 块之后的代码可能永远无法到达。

来自javatutorial

finally 块总是在 try 块退出。这确保了 finally 块被执行,即使 发生意外异常。但 finally 不仅有用 异常处理——它允许 程序员避免清理 代码意外绕过了 返回、继续或中断。推杆 finally 块中的清理代码是 总是一个好习惯,即使没有 预计会有例外情况。

【讨论】:

  • @articlestack 还记得在 System.exit(); 时不会调用 finally 块;被调用。
  • @Suresh S:好点子,但无论如何在 System.exit 的情况下......什么都不重要......(资源在操作系统级别释放)
  • @helios 你不能一概而论,特别是如果进程正在利用另一个进程的资源。
  • 即使从 catch 块中抛出异常,最终是否也会执行?
【解决方案4】:

如果我理解了这个问题,那么你问的是:

try {
    Foo f = new Foo();
    f.bar();
}
finally
{
    Foo.baz();
}

还有:

// This doesn't actually compile because a try block needs a catch and/or finally block
try {
    Foo f = new Foo();
    f.bar();
}
Foo.baz();

或者,更有可能:

Foo f = new Foo();
f.bar();
Foo.baz();

不同之处在于,如果new Foo()f.bar() 抛出异常,则finally 块将在第一种情况下被执行,但Foo.baz() 在最后两种情况下不会被执行:相反当 JVM 查找异常处理程序时,控件将跳过 Foo.baz()


编辑

回应你的评论,怎么样:

Foo f = new Foo();
try {
    f.bar();
}
catch (Exception ex)
{
    // ...
}

f.baz();

您是对的,假设catch 块没有重新抛出异常,或者从指示发生故障的方法返回,那么无论是否存在异常,都会调用f.baz()。然而,即使在这种情况下,finally 块也可用作 f.baz() 用于清理的文档。

更重要的是,通常抛出异常这一事实很重要,因此很难编写在不知道抛出异常的情况下继续执行其正在执行的操作的代码。有时异常表示您可以忽略的愚蠢事情,在这种情况下,您应该吞下异常。然而,更常见的情况是,您希望通过重新抛出异常(或抛出不同的异常)或从带有错误代码的方法返回来发出失败信号。

例如,如果f.bar() 应该将String 转换为Double,并且在失败时会抛出NumberFormatException,那么try 块之后的代码可能需要知道@987654341 @ 实际上并未转换为 Double。而且,一般来说,您不会希望在 catch 块之后继续。相反,你会想要发出失败的信号。这被称为“失败时中止”(与“失败时恢复”相比,它可能应该被称为“失败后蒙混过关”)。

除了,在特殊情况下你可能会蒙混过关。例如,catch 块可以将相关的Double 设置为Double.NaN,这是专门为在数学表达式中正确传播错误而设计的。即使在这种情况下,finally 块也可以作为 f.baz() 参与某种清理的文档。

【讨论】:

  • 我是说试着抓住两个块。现在最终需要什么?因为如果捕获到错误并采取了特定操作,则应执行下一条语句(finally{} 或 catch{} 之后的语句)。
  • @aticlestak:我已经编辑了这篇文章,希望能回答你的问题。您描述的案例开启了关于如何使用异常的哲学。
【解决方案5】:

finally 块包含应该执行的代码行,无论是否捕获到异常。即使您决定停止以该方法运行的代码。所以 t-c-f 之后的代码可能不会被执行,但是 finally 代码是“保证的”(保证在不崩溃的意义上立即破坏不可处理的错误)。

【讨论】:

  • 如果 try...catch 在那里。但是我在没有finally块或finally块的情况下编写rest语句?有什么区别?
【解决方案6】:

是的,会有一些非常严重的错误。

也就是说,您的代码只有在出现错误时才会运行

finally 中的语句始终运行,无论是否引发异常。这就是重点。

【讨论】:

  • 清理代码,例如,无论是否有错误,您都希望运行。这属于 finally 块。
  • 你可以通过添加 Throw new Exception(); 来确保它每次都出错。作为 Try 块中的最后一行 LOL
  • 我认为您误解了“catch 块之后的所有语句”,对我来说这听起来像 try { ...; } 抓住 { ....; } 所有语句;
  • @JumpingJezza:是的,但该语句令人困惑,因为 finally 中的代码本身并不在 try 块中,因此需要像其他任何地方一样捕获那里的错误。您在 finally 中的代码应该是健壮的,这样它就会主动地不产生异常。
  • @Maxem:这是一种解释,但我无法想象问它的目的,因为被它弄糊涂会很奇怪......
【解决方案7】:

finally 块特别用在异常预防的时候。如果发生任何运行时错误,程序可能会导致终止。所以这个时候,它会在去终止程序之前调用finally块。通常'finally'包含连接关闭语句、保存操作和文件输入、输出关闭操作。

【讨论】:

    【解决方案8】:

    如果您的代码从不抛出异常,或者您正在消耗所有正确的异常。并非总是如此。

    【讨论】:

      【解决方案9】:

      问题确实在这里得到了回答(how to use finally

      我对答案的理解是:在某些情况下,我们会抛出异常块无法处理的异常。在那种情况下你会怎么做?你想继续前进吗?不是个好主意!你最好打印一些东西,表明你通过了一个可能发生或可能没有发生异常的阶段,如果发生异常,它应该由 catch 语句处理,如果它没有被处理,可能会有一些不匹配的异常

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-08
        • 1970-01-01
        • 2016-07-16
        • 1970-01-01
        • 2012-03-13
        • 2020-01-29
        相关资源
        最近更新 更多