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个回答

10

finally 块总是在返回 x (已计算的) 值之前执行。

System.out.println("x value from foo() = " + foo());

...

int foo() {
  int x = 2;
  try {
    return x++;
  } finally {
    System.out.println("x value in finally = " + x);
  }
}

输出:

finally中的x值=3
来自foo()函数的x值=2


很好的说明性例子,加一。 - President James K. Polk

9

作为对@vibhash的回答的补充,因为没有其他回答解释下面这个可变对象的情况。

public static void main(String[] args) {
    System.out.println(test().toString());
}

public static StringBuffer test() {
    StringBuffer s = new StringBuffer();
    try {
        s.append("sb");
        return s;
    } finally {
        s.append("updated ");
    }
}

将会输出

sbupdated 

截至Java 1.8.162,这不是输出。 - sam

9
是的,它会执行。无论在 try 或 catch 块中发生什么事情,除非调用 System.exit() 或者 JVM 崩溃。如果块中有任何返回语句,finally 将在该返回语句之前被执行。

8

考虑以下程序:

public class SomeTest {

    private static StringBuilder sb = new StringBuilder();

    public static void main(String args[]) {

        System.out.println(someString());
        System.out.println("---AGAIN---");
        System.out.println(someString());
        System.out.println("---PRINT THE RESULT---");
        System.out.println(sb.toString());
    }

    private static String someString() {

        try {
            sb.append("-abc-");
            return sb.toString();

        } finally {
            sb.append("xyz");
        }
    }
}

截至Java 1.8.162,上述代码块会输出以下内容:
-abc-
---AGAIN---
-abc-xyz-abc-
---PRINT THE RESULT---
-abc-xyz-abc-xyz

这意味着使用 finally 来释放对象是一个好的实践,就像以下代码一样:
private static String someString() {

    StringBuilder sb = new StringBuilder();

    try {
        sb.append("abc");
        return sb.toString();

    } finally {
        sb = null; // Just an example, but you can close streams or DB connections this way.
    }
}

finally 里面应该是 sb.setLength(0) 吧? - user7294900
sb.setLength(0) 只会清空 StringBuffer 中的数据。因此,sb = null 将会解除对象与引用之间的关联。 - sam
“xyz”在输出中不应该打印两次吗?既然函数被调用了两次,为什么“finally”只出现了一次? - fresko
@fresko:最终肯定会被调用两次,但由于返回语句的存在,控制台输出只会显示一次。您可以运行程序并查看输出,然后添加另一个语句 System.out.println(sb.toString()); - sam
3
这不是一个好的做法。在 sb = null;finally 块只是增加了不必要的代码。我知道你的意思是 finally 块是释放资源(如数据库连接等)的好地方,但请记住,这个例子可能会让新手感到困惑。 - Roc Boronat
1
@Samim 谢谢,我添加了以下代码行: System.out.println("---AGAIN2---"); System.out.println(sb); 现在更加清晰了。之前的输出与你的论点相反 :p 我还添加了一些内容到你的答案中,但是编辑必须由管理员或类似人员接受。否则,你可以添加它们。 - fresko

8
我尝试了这个,它是单线程的。
public static void main(String args[]) throws Exception {
    Object obj = new Object();
    try {
        synchronized (obj) {
            obj.wait();
            System.out.println("after wait()");
        }
    } catch (Exception ignored) {
    } finally {
        System.out.println("finally");
    }
}
main Thread将永远处于wait状态,因此finally将永远不会被调用,所以控制台输出将不会在wait()finally后打印String
同意@Stephen C的看法,上述示例是提到这里的第三种情况之一。
在以下代码中添加一些更多的无限循环可能性:
// import java.util.concurrent.Semaphore;

public static void main(String[] args) {
    try {
        // Thread.sleep(Long.MAX_VALUE);
        // Thread.currentThread().join();
        // new Semaphore(0).acquire();
        // while (true){}
        System.out.println("after sleep join semaphore exit infinite while loop");
    } catch (Exception ignored) {
    } finally {
        System.out.println("finally");
    }
}

情况2:如果JVM先崩溃

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public static void main(String args[]) {
    try {
        unsafeMethod();
        //Runtime.getRuntime().halt(123);
        System.out.println("After Jvm Crash!");
    } catch (Exception e) {
    } finally {
        System.out.println("finally");
    }
}

private static void unsafeMethod() throws NoSuchFieldException, IllegalAccessException {
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    unsafe.putAddress(0, 0);
}

参考:如何使JVM崩溃?

情况六:如果finally块将由守护线程执行,并且在调用finally之前所有其他非守护线程都退出。

public static void main(String args[]) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                printThreads("Daemon Thread printing");
                // just to ensure this thread will live longer than main thread
                Thread.sleep(10000);
            } catch (Exception e) {
            } finally {
                System.out.println("finally");
            }
        }
    };
    Thread daemonThread = new Thread(runnable);
    daemonThread.setDaemon(Boolean.TRUE);
    daemonThread.setName("My Daemon Thread");
    daemonThread.start();
    printThreads("main Thread Printing");
}

private static synchronized void printThreads(String str) {
    System.out.println(str);
    int threadCount = 0;
    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
    for (Thread t : threadSet) {
        if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {
            System.out.println("Thread :" + t + ":" + "state:" + t.getState());
            ++threadCount;
        }
    }
    System.out.println("Thread count started by Main thread:" + threadCount);
    System.out.println("-------------------------------------------------");
}

输出:这不会打印“finally”,这意味着“守护线程”中的“finally块”没有执行。
main Thread Printing  
Thread :Thread[My Daemon Thread,5,main]:state:BLOCKED  
Thread :Thread[main,5,main]:state:RUNNABLE  
Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE   
Thread count started by Main thread:3  
-------------------------------------------------  
Daemon Thread printing  
Thread :Thread[My Daemon Thread,5,main]:state:RUNNABLE  
Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE  
Thread count started by Main thread:2  
-------------------------------------------------  

Process finished with exit code 0

4
请看已接受的答案。这只是“无限循环”的一个边缘情况。 - Stephen C

8

是的,finally块始终会执行。大多数开发人员使用该块关闭数据库连接、结果集对象、语句对象,并在Java Hibernate中用于回滚事务。


8

finally 一定会被执行。

finally 不会在以下情况下执行:

情况1:

当你执行 System.exit() 时。

情况2:

当你的 JVM / Thread 崩溃时。

情况3:

当你的执行在手动停止之前被中断时。


8
是的,它会。 唯一不会的情况是JVM退出或崩溃。

7

在不同的论坛上看到很多答案,让我感到非常困惑,所以决定自己动手编写代码来验证。输出结果如下:

即使try和catch块中有return语句,finally块也会被执行。

try {  
  System.out.println("try"); 
  return;
  //int  i =5/0;
  //System.exit(0 ) ;
} catch (Exception e) {   
  System.out.println("catch");
  return;
  //int  i =5/0;
  //System.exit(0 ) ;
} finally {  
   System.out.println("Print me FINALLY");
}

输出

尝试

最后打印我

  1. 如果在上述代码中try和catch块中的return被替换为System.exit(0),并且由于任何原因在此之前发生异常,则会出现异常。

7
  1. 最终块总是会被执行,除非存在System.exit()语句(在最终块的第一条语句中)。
  2. 如果system.exit()是第一条语句,则最终块不会被执行,并且控制流程将离开最终块。每当System.exit()语句出现在finally块中时,直到该语句finally块被执行,当System.exit()出现时,控制强制从finally块中退出。

3
这个问题已经被回答很多次了,那么你的回答添加了哪些新信息呢? - Tom

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