finally块的作用是什么?

35

除去语法,两者之间有什么区别?

try {
}
catch() {
}
finally {
    x = 3;
}
and
try {
}
catch() {
}

x = 3;
编辑:在.NET 2.0中吗?

所以

try {
    throw something maybe
    x = 3
}
catch (...) {
    x = 3
}

行为等效吗?


您可能需要指定编程语言。行为将取决于语言。 - Eric Z Beard
为了决定最佳答案,我认为您需要澄清try/catch块是否为空。正如其中一个答案所述,如果try/catch块为空,则语义是相同的,否则不同的答案会涉及语义差异。 - Kasper
我同意之前的评论,你需要澄清实际使用的编程语言,并认真审查给出的答案。被点赞的答案要么是不正确的,要么是不完整的。 - Rob Cooper
16个回答

45

首先,如果在try块内使用RETURN语句,则finally块仍会执行,但位于try-catch-finally块下方的代码将不会被执行。


1
你说得对。这是一个好回答,但我想它并没有直接回答所提出的问题。也许这意味着我应该问一个更好的问题 :) - Keshi
这里的每个人都是正确的。我想我只是想要简洁明了。我注意到早期的答案得到了所有的赞。;-) - Josh Hinman
这正是我怀疑的,我很感谢确认。虽然简短,但提供了正确的答案。 - Paul Russell

36

根据语言的不同,可能会存在一些细微的语义差别,但大致意思是当代码块中出现异常时,finally块将(几乎)总是被执行。

在第二个示例中,如果catch块中的代码返回或退出,则x = 3不会被执行。而在第一个示例中,它会被执行。

在.NET平台上,有些情况下finally块的执行不会发生: 安全性异常、线程挂起、计算机关闭等。


2
我想要将阻止 finally 代码块执行的方法列表中加入 Environment.Exit 调用。 - Luca

10

在Java中:

无论异常是否被正确捕获于catch()中,或者你根本没有用catch语句,finally块都会被调用。


1
"Always"是一个非常强烈的词。 - mattumotu

9

try catch finally 是非常重要的结构。你可以确信,即使抛出异常,finally块中的代码也会被执行。这在处理外部资源以释放它们方面非常重要。垃圾回收并不会为您完成此操作。在finally部分中,您不应该有return语句或抛出异常。虽然可能做到这一点,但这是一个不好的实践,并可能导致不可预测的结果。

如果您尝试这个例子:

try {
  return 0;
} finally {
  return 2;
}

结果将会是2 :)
与其他语言的比较:Finally块中的返回

请记住,Finally块并不总是被执行。堆栈溢出(非双关语)会导致Finally块无法执行。 - David Basarab
3
Java中的结果将是2。在C#中,这段代码无法编译。 - thorn0

6

finally块之所以有用,原因如下:

  1. 如果你从try或catch块中返回,那么在返回到调用函数之前,finally块将被执行。
  2. 如果catch块中发生异常或在try块中出现未捕获的异常类型,finally块中的代码仍将被执行。

这些特点使得finally块非常适合用于关闭文件句柄或套接字。


3
如果try和catch都是空的话,二者没有区别。否则你可以确定finally语句块会被执行。
例如在catch块中抛出一个新的异常(重新抛出),那么只有在finally语句块中才会执行该赋值。
通常情况下,finally语句块用于清理操作(关闭数据库连接、文件句柄等)。
永远不要在finally语句块中使用控制语句(return, break, continue),因为这会造成维护上的麻烦,被认为是一种不良的编程习惯。

2

finally块与try/catch在同一作用域内,因此您将可以访问在其中定义的所有变量。

假设您有一个文件处理程序,这是编写方式的不同之处。

try
{
   StreamReader stream = new StreamReader("foo.bar");
   stream.write("foo");
}
catch(Exception e) { } // ignore for now
finally
{
   stream.close();
}

相对于
StreamReader stream = null;
try
{
    stream = new StreamReader("foo.bar");
    stream.write("foo");
} catch(Exception e) {} // ignore

if (stream != null)
    stream.close();

请记住,任何在finally块中的代码都不能保证一定会执行。想象一下,如果出现了中断信号、Windows崩溃或者电源故障等情况,那么finally块中的代码就无法执行了。因此,在关键业务代码中依赖finally是不可取的。


请定义一种编程语言,因为这在你的回答中是暗示但没有明确说明的。 - shsteimer
一个stackoverflow也会导致finally块不执行。 - David Basarab
1
我认为第一个代码示例是不正确的(在C#中) - 在“try”块中声明的变量在catch或finally块中在范围内。我在测试C#应用程序中尝试过,并在finally块引用上获得了编译时错误:“当前上下文中不存在名称'stream'”。 - Jon Schneider

1

最终块允许您作为开发人员在try{}块中遇到错误的前置代码的操作后,无论如何都可以整理自己的代码,正如其他人指出的那样,这主要涉及释放资源-关闭指针/套接字/结果集,将连接返回到池中等。

@mats非常正确,总会存在“硬”故障的潜在可能性-finally块不应包含关键任务代码,这些代码应始终在try{}内部以事务方式完成

@mats再次强调-真正美妙之处在于它允许您将异常抛回到自己的方法中,并仍然保证您的代码整洁:

try
{
StreamReader stream = new StreamReader("foo.bar");
mySendSomethingToStream(stream);
}
catch(noSomethingToSendException e) {
    //Swallow this    
    logger.error(e.getMessage());
}
catch(anotherTypeOfException e) {
    //More serious, throw this one back
    throw(e);
}
finally
{
stream.close();
}

所以,我们可以捕获许多类型的异常,以不同的方式处理它们(第一个允许执行try{}之外的任何内容,第二个则有效地返回),但始终要整洁而有条理地清理干净。

1

@iAn 和 @mats:

我不会在 finally {} 中“拆除”在 try {} 中“设置”的任何内容。最好将流的创建移到 try {} 之外。如果需要处理流创建时的异常,可以在更大的范围内完成。

StreamReader stream = new StreamReader("foo.bar");  
try {
    mySendSomethingToStream(stream);
}
catch(noSomethingToSendException e) {
    //Swallow this    
    logger.error(e.getMessage());
}
catch(anotherTypeOfException e) {
    //More serious, throw this one back
    throw(e);
}
finally {
    stream.close();  
}

1
这里需要注意的是,代码行 throw(e); //rewrites stack tracethrow; //re-throws the original exception 是不同的。 - vitule

1
这不是一个答案,而是一篇评论。这个问题很 老旧,但这一直困扰着我。我在这里找到了它的原因。我读了每一个答案,看起来没有人真正深思熟虑过。

我真的认为finally没有好处,这可能是为什么它直到“最近”才出现在编程语言中的原因。大多数例子说明stream.close()可能会导致空引用异常,因此您仍然必须测试它是否为空。

是的,如果你在try{}中返回,finally仍然运行。但这是好的做法吗?这似乎像是心理上的体操,还不如把goto带回来。为什么不等待,在块之后返回?所有finally {}所做的就是给你的代码添加两三行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接