Java中的finally块是否总是会被执行?

2684
考虑到这段代码,我能否绝对确定无论 something() 是什么,finally 语句块总是会被执行?
try {  
    something();  
    return success;  
}  
catch (Exception e) {   
    return failure;  
}  
finally {  
    System.out.println("I don't know if this will get printed out");
}

61
不总是。 - Boann
3
《Effective Java》认为情况并非如此。http://www.informit.com/articles/article.aspx?p=1216151&seqNum=7 - Binoy Babu
36
@BinoyBabu,_finalizer_不等于finally;_finalizer_指的是finalize()方法。 - jaco0646
4
没错,“不总是”是正确的。但是这样你就永远不能使用“保证”或“总是”这些词了。 - MC Emperor
2
@Boann 我会这样表达:在跳出 try-finally 结构之前,执行流程总是会经过 finally。如果它在其中失败了,我也没关系,因为 finally 的主要目的是确保其他代码部分不会出现混乱。 - Imperishable Night
显示剩余3条评论
52个回答

3
如果在嵌套的finally块内部抛出异常,也可以提前退出finally。编译器会警告你finally块不正常完成或者给出一个无法访问的代码错误。无法访问代码的错误只有在throw不在条件语句后面或循环内部时才会显示。
try {
} finally {
   try {
   } finally {
      // if (someCondition) --> no error because of unreachable code
      throw new RunTimeException();
   }

   int a = 5; // unreachable code
}

1
只是注意到这是完全正常的行为。如果抛出异常,任何/所有代码都会提前退出。这与在其他任何地方抛出异常没有区别。规范中没有特殊规则说“不能在finally块内使用异常”。因此,一开始我甚至误解了你的答案,差点给了-1,直到我重新阅读了几次。 - Loduwijk
那么,返回语句和抛出异常有什么不同呢?如果你使用返回语句,那么代码块应该会返回,但是在 finally 代码块中,返回语句会被推迟执行,并且对于返回语句还有一些奇怪的异常情况,因此不能确定 finally 是否以正常方式处理抛出异常。这就是当时我写这个的主要原因。此外,在 try 或 catch 代码块中抛出异常并不会阻止 finally 代码块的执行,因此当你在 finally 代码块中抛出异常时,它仍然会执行。 - HopefullyHelpful
1
“return”和“throw”有哪些不同之处?有几个方面不同,但这里都不相关。在 finally 块中,return 不能被延迟执行,或者你是指 try 块中的 return 被延迟到 finally 块执行后再执行(如果是这种情况,那么是的,确实如此)?无论如何,抛出的异常也是一样的。无论在 try 中发生什么事情,在达到 try 的结尾时,控制权都会传递给 finally,然后所有相同的规则都适用于 finally(finally 中的任何 try/catch 都像通常情况下一样运行)。 - Loduwijk
2
我并不是说你的答案是错误或无用的,从技术上讲它是正确的。我只是注意到你描述的行为并没有违反try/finally规则,这个规则简单来说就是“无论在try/catch中发生了什么,一旦完成,控制流程将会传递到finally块”。try/finally保证本质上就是这样,没有更多也没有更少。在任何块中,无论是try/catch/finally还是其他块,你都可以像你建议的那样嵌套其他的try/catch/finally块。事实上,有些人并不知道这一点。 - Loduwijk

3

由于无论你遇到什么情况,最终总是会调用 finally 块。即使没有异常出现,finally 块仍然会被调用。


3
在正常执行过程中考虑以下情况(即没有抛出任何异常):如果方法不是'void',则它总是会明确地返回某些内容,然而,finally子句总是会被执行。

3

与以下代码相同:

static int f() {
    while (true) {
        try {
            return 1;
        } finally {
            break;
        }
    }
    return 2;
}

f将返回2!


重复的答案 - Stephen C
但这表明无论如何,最终块都会被执行。 - Shivam Agarwal

3

是的,在任何情况下它都会被调用,但是当你使用 System.exit() 时不会被调用。

try {
    // risky code
} catch (Exception e) {
    // exception handling code
}
finally() {
    // It is always executed, but if before this block there is any statement like System.exit(0); then this block will not be executed.
}

重复的答案 - Stephen C
不仅是“重复的”,而且自相矛盾。 - user207421

2

无论您在try块中放置了一个return语句,finally块始终会被执行。在return语句之前,finally块将被执行。


2

最后总是在结尾处调用

当您尝试执行某些代码时,如果try中发生了异常,catch将捕获该异常并可以打印一些消息或抛出错误,然后finally块将被执行。

通常在进行清理时使用finally,例如,如果您在Java中使用扫描仪,则应该关闭扫描仪,因为否则会导致其他问题,例如无法打开某些文件。


2
以下是一些可以绕过finally块的条件:
  1. 如果JVM在执行try或catch代码时退出,则finally块可能不会执行。更多信息请参见Sun教程
  2. 正常关闭 - 当最后一个非守护线程退出或Runtime.exit() (一些好的博客)时会发生这种情况。当一个线程退出时,JVM会对正在运行的线程进行清单,如果只剩下守护线程,它会启动有序关闭。当JVM停止时,任何剩余的守护线程都将被放弃,finally块不会被执行,堆栈也不会被卸载,JVM就退出了。应该节约使用守护线程,很少有处理活动能够安全地随时被放弃而无需清理。特别地,使用守护线程执行可能执行任何I/O操作的任务是危险的。守护线程最好用于“管理”任务,例如定期从内存中的缓存中删除过期条目的后台线程(来源)。

最后一个非守护线程退出的示例:

public class TestDaemon {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                while (true) {
                    System.out.println("Is alive");
                    Thread.sleep(10);
                    // throw new RuntimeException();
                }
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                System.out.println("This will never be executed.");
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread daemon = new Thread(runnable);
        daemon.setDaemon(true);
        daemon.start();
        Thread.sleep(100);
        // daemon.stop();
        System.out.println("Last non-daemon thread exits.");
    }
}

Output:

Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Last non-daemon thread exits.
Is alive
Is alive
Is alive
Is alive
Is alive

1
尝试使用资源示例
static class IamAutoCloseable implements AutoCloseable {
    private final String name;
    IamAutoCloseable(String name) {
        this.name = name;
    }
    public void close() {
        System.out.println(name);
    }
}

@Test
public void withResourceFinally() {
    try (IamAutoCloseable closeable1 = new IamAutoCloseable("closeable1");
         IamAutoCloseable closeable2 = new IamAutoCloseable("closeable2")) {
        System.out.println("try");
    } finally {
        System.out.println("finally");
    }
}

测试输出:

try
closeable2
closeable1
finally

0

被接受的答案在几乎所有方面都是正确的,但它仍然只是真相的一半(好吧,95%的真相)。

假设以下代码:

private final Lock m_Lock = new ReentrantLock();
…
public final SomeObject doSomething( final SomeObject arg )
{
  final SomeObject retValue;
  try
  {
    lock.lock();
    retValue = SomeObject( arg );
  }
  finally
  {
    out.println( "Entering finally block");
    callingAnotherMethod( arg, retValue );
    lock.unlock();
  }
  
  return retValue;
}
…
try
{
   final var result = doSomething( new SomeObject() );
}
catch( final StackOverflowError e ) { /* Deliberately ignored */ }

调用方法doSomething()会立即导致StackOverflowError

lock将不会被释放!

但是,当finally块总是执行(已在接受的答案中列出的异常除外)时,怎么会发生这种情况呢?

那是因为没有保证finally块中的所有语句都真正执行!

如果在调用lock.unlock()之前有一个对System.exit()throws语句的调用,那么这一点就显而易见了。

但是,在示例代码中没有这样的内容...

除此之外,在调用lock.unlock()之前finally块中的另外两个方法调用将导致另一个StackOverflowError...

然后,锁未被释放!

尽管样例代码本身很傻,但类似模式在许多种软件中都可以找到。只要在finally块中没有发生任何问题,一切都运作正常...

有趣的事实是,它在Java的后续版本中不起作用(意味着在后续版本中,锁被释放了...)。不知道这个什么时候和为什么改变了。

但你仍然必须确保finally块始终正常终止,否则它可能无所谓是否被执行...


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