是否finally块总是用Java执行?


2382

考虑到这段代码,无论是什么,我是否可以绝对确定finally块始终执行something()

try {  
    something();  
    return success;  
}  
catch (Exception e) {   
    return failure;  
}  
finally {  
    System.out.println("I don't know if this will get printed out");
}

471
如果没有,则应probably改用关键字命名。
中午




27
@BinoyBabu,终结器!= finally; finalizer == finalize()方法。
jaco0646 '16

Answers:


2697

是的,finally将在执行trycatch代码块后调用。

唯一finally不会被调用的时间是:

  1. 如果您调用 System.exit()
  2. 如果您调用 Runtime.getRuntime().halt(exitStatus)
  3. 如果JVM首先崩溃
  4. 如果JVM在trycatch块中达到了无限循环(或其他不可中断,不终止的语句)
  5. 操作系统是否强行终止了JVM进程;例如,kill -9 <pid>在UNIX上
  6. 如果主机系统死机;例如,电源故障,硬件错误,操作系统崩溃等
  7. 如果该finally块将由守护程序线程执行并且所有其他非守护程序线程在finally调用之前退出

44
实际上thread.stop()并不一定阻止finally执行块。
Piotr Findeisen

181
我们怎么说,该finally块将try块之后,控制权传递给以下语句之前被调用。这与try块涉及无限循环是一致的,因此finally块永远不会真正被调用。
Andrzej Doyle

9
还有另一种情况,当我们使用嵌套的try-catch-finally块时
ruhungry 2014年

7
同样,如果守护程序线程抛出异常,则不会调用finally块。
Amrish Pandey 2014年

14
@BinoyBabu-那是关于终结器,而不是最终阻止
avmohan

567

示例代码:

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

public static int test() {
    try {
        return 0;
    }
    finally {
        System.out.println("finally trumps return.");
    }
}

输出:

finally trumps return. 
0

18
仅供参考:在C#中,行为是相同的,除了不允许将finally-clause中的语句替换为return 2;(Compiler-Error)。
2013年

15
这是一个需要注意的重要细节:stackoverflow.com/a/20363941/2684342
WoodenKitty 2013年

18
您甚至可以在finally块本身中添加return语句,该语句将覆盖先前的返回值。这也神奇地丢弃了未处理的异常。那时,您应该考虑重构代码。
Zyl 2015年

8
但这并不能真正证明王牌最终会回归。返回值是从调用者代码中打印出来的。似乎证明不多。
Trimtab '16

20
抱歉,但这不是演示而是证明。仅当您可以证明此示例在所有Java平台上始终以这种方式运行并且类似的示例也始终以这种方式运行时,才可以证明这一点。
斯蒂芬C,

391

另外,尽管这是不好的做法,但是如果finally块中有return语句,它将胜过常规块中的其他任何返回。也就是说,以下块将返回false:

try { return true; } finally { return false; }

从finally块中抛出异常也是一样。


95
这是一个非常糟糕的做法。有关为什么它不好的更多信息,请参见stackoverflow.com/questions/48088/…
John Meagher's

23
同意 在finally {}中的返回将忽略在try {}中引发的任何异常。害怕!
neu242

8
@ dominicbri7为什么您认为这是更好的做法?当函数/方法无效时,为什么又应该有所不同呢?
corsiKa 2011年

8
出于同样的原因,我从不在我的C ++代码中使用goto。我认为多次返回使阅读变得更困难,调试也更加困难(当然,在非常简单的情况下,它并不适用)。我想这只是个人偏爱,最后您可以使用这两种方法实现相同的目的
dominicbri7 2011年

16
当发生某种例外情况时,我倾向于使用大量回报。就像if(有理由不继续)返回一样;
iHearGeoff

257

这是Java语言规范中的正式词汇。

14.20.2。最终尝试和最终捕获的执行

通过首先执行该块来执行try带有finally块的语句try。然后有一个选择:

  • 如果该try块的执行正常完成,则[...]
  • 如果try由于a throw的值V导致块的执行突然完成,则[...]
  • 如果try由于任何其他原因R突然终止了该finally块的执行,则执行该块。然后有一个选择:
    • 如果finally块正常完成,则该try语句由于原因R突然完成。
    • 如果该finally块由于原因S突然完成,那么该try语句由于原因S突然完成(并且丢弃了原因R)。

的规范return实际上明确了这一点:

JLS 14.17返回声明

ReturnStatement:
     return Expression(opt) ;

一条return没有Expression 试图将控制权转移到包含该方法的方法或构造函数的调用方的语句。

return与语句Expression 尝试将控制转移到包含它的方法的调用; 的值Expression成为方法调用的值。

前面的描述说的是“ 尝试转移控制权 ”,而不仅仅是“ 转移控制权 ”,因为如果try方法或构造函数中有任何语句的语句try块包含该return语句,则finally这些try语句的任何子句将按从内到外的顺序执行。 ,然后将控制权转移到方法或构造函数的调用者。finally子句的突然完成会破坏由return语句启动的控制权的转移。


163

除了其他响应之外,重要的一点是要指出,“最终”有权通过try..catch块覆盖任何异常/返回的值。例如,以下代码返回12:

public static int getMonthsInYear() {
    try {
        return 10;
    }
    finally {
        return 12;
    }
}

同样,以下方法不会引发异常:

public static int getMonthsInYear() {
    try {
        throw new RuntimeException();
    }
    finally {
        return 12;
    }
}

尽管以下方法确实将其抛出:

public static int getMonthsInYear() {
    try {
        return 12;          
    }
    finally {
        throw new RuntimeException();
    }
}

63
应该注意的是,中间情况恰好是为什么在finally块中包含return语句绝对可怕的原因(它可能隐藏任何Throwable的原因)。
Dimitris Andreou 2010年

2
希望有一个surpressed OutOfMemoryError?;)
RecursiveExceptionException

我对其进行了测试,它确实抑制了此类错误(是的!)。当我编译它时也会生成警告(是!)。您可以通过定义一个返回变量,然后return retVal finally块之后使用它来解决该问题,尽管这当然是假设您抑制了其他一些异常,因为否则该代码将毫无意义。
Maarten Bodewes

120

我尝试对上面的示例进行了一些修改-

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

public static int test() {
    int i = 0;
    try {
        i = 2;
        return i;
    } finally {
        i = 12;
        System.out.println("finally trumps return.");
    }
}

上面的代码输出:

最终王牌归来。
2

这是因为return i;执行时i的值是2。此后,finally执行该块,其中分配了12 i,然后System.out执行out。

执行完该finally块后,该try块返回2,而不是返回12,因为不会再次执行该return语句。

如果您将在Eclipse中调试此代码,则您会感觉到执行完block 语句后,再次执行System.outfinallyblock return语句try。但这种情况并非如此。它只是返回值2。


10
这个例子很棒,它增加了许多最终相关线程中未提及的内容。我认为几乎没有任何开发人员会知道这一点。
HopefulHelpful

4
如果i不是原始对象,而是整数对象,该怎么办?
Yamcha

我很难理解这个案子。docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.17说,“带有Expression的return语句试图将控制权转移给包含以下内容的方法或lambda主体的调用者它...。如果对表达式的求值正常完成,则生成一个值V。不会影响返回的值,请纠正我。
meexplorer

但是我找不到关于此的任何证据,在哪里提到return不再评估表达式。
meexplorer

1
@meexplorer有点晚,但是在JLS 14.20.2中有解释try-finally和try-catch-finally的执行 -有点复杂,14.17。还必须阅读return语句
user85421 '17

117

这是凯文答案的详细说明。重要的是要知道要返回的表达式是在之前求值的finally,即使它是在之后返回的也是如此。

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

public static int printX() {
    System.out.println("X");
    return 0;
}

public static int test() {
    try {
        return printX();
    }
    finally {
        System.out.println("finally trumps return... sort of");
    }
}

输出:

X
finally trumps return... sort of
0

8
重要提示。
Aminadav Glickshtein

很高兴知道,并且也很有意义。似乎实际上返回的是返回的值finally。计算返回值(printX()此处)仍在此之前。
艾伯特

所有3个“返回”点的好例子!
radistao

不。上面的代码应替换System.out.println("finally trumps return... sort of");System.out.print("finally trumps return in try"); return 42;
Pacerier

54

那就是finally块的整个想法。它可以确保您进行清理,否则您可能会跳过清理,因为您除其他外当然会返回。

无论 try块中发生了什么, Finally都会被调用(除非您调用System.exit(int)或Java Virtual Machine出于某些其他原因被踢出)。


那是一个极其薄弱的答案。stackoverflow.com/a/65049/715269
Gangnus

42

考虑这一点的逻辑方法是:

  1. 无论 try块内发生什么,都必须执行放置在finally块中的代码
  2. 因此,如果try块中的代码尝试返回值或引发异常,则将该项目“放在架子上”,直到finally块可以执行为止
  3. 因为finally块中的代码(根据定义)具有较高的优先级,所以它可以返回或抛出任何喜欢的东西。在这种情况下,“架子上”剩下的任何东西都将被丢弃。
  4. 唯一的例外是VM是否在try块中完全关闭,例如通过“ System.exit”关闭

10
这仅仅是“一种思考的逻辑方式”还是真的根据规范规范了finally块的工作方式?在这里,指向Sun资源的链接将非常有趣。
matias 2010年

21

除非有异常的程序终止(例如调用System.exit(0)..),否则总是执行finally。因此,您的sysout将被打印



18

除非由于JVM崩溃或对的调用导致程序异常终止,否则总是执行finally块System.exit(0)

最重要的是,从finally块内返回的任何值都将覆盖执行finally块之前返回的值,因此在使用try finally时请务必检查所有退出点。


18

不,并非总是一种例外情况是// System.exit(0); 在finally块阻止finally被执行之前。

  class A {
    public static void main(String args[]){
        DataInputStream cin = new DataInputStream(System.in);
        try{
            int i=Integer.parseInt(cin.readLine());
        }catch(ArithmeticException e){
        }catch(Exception e){
           System.exit(0);//Program terminates before executing finally block
        }finally{
            System.out.println("Won't be executed");
            System.out.println("No error");
        }
    }
}

这就是您真正不应该调用System.exit()的原因之一
Franz D.

13

总而言之,总是运行finally,只是因为它在返回之后出现在代码中并不意味着它就是这样实现的。退出try块时,Java运行时负责运行此代码。

例如,如果您具有以下条件:

int foo() { 
    try {
        return 42;
    }
    finally {
        System.out.println("done");
    }
}

运行时将生成如下内容:

int foo() {
    int ret = 42;
    System.out.println("done");
    return 42;
}

如果抛出未捕获的异常,则该finally块将运行,并且该异常将继续传播。


11

这是因为您将i的值分配为12,但没有将i的值返回给函数。正确的代码如下:

public static int test() {
    int i = 0;
    try {
        return i;
    } finally {
        i = 12;
        System.out.println("finally trumps return.");
        return i;
    }
}

10

因为finally块将始终被调用,除非您调用System.exit()(否则线程崩溃)。


10

简而言之,在正式的Java文档(单击此处)中,写道-

如果在执行try或catch代码时JVM退出,则finally块可能不会执行。同样,如果执行try或catch代码的线程被中断或杀死,即使整个应用程序继续运行,finally块也可能不会执行。


10

答案很简单YES

输入:

try{
    int divideByZeroException = 5 / 0;
} catch (Exception e){
    System.out.println("catch");
    return;    // also tried with break; in switch-case, got same output
} finally {
    System.out.println("finally");
}

输出:

catch
finally

1
答案很简单。
Christophe Roussy

1
@ChristopheRoussy怎么样?你能解释一下吗?
见面

1
阅读已接受的答案,最初的问题是关于“它将始终执行”,而并非总是如此。以您的情况,它将但不能解决原始问题,甚至可能会误导初学者。
Christophe Roussy

那么在那种情况下它将无法执行?
见面

在其他答案中提到的所有情况下,请参阅接受的答案并带有1000多个投票。
Christophe Roussy

9

是的,它将被调用。这就是拥有finally关键字的全部意义。如果跳出try / catch块可以跳过finally块,则与将System.out.println放在try / catch外部相同。



9

不总是

Java语言规范描述了如何try- catch- finallytry- catch块在工作14.20.2
它在任何地方指定该finally块总是执行。但对于所有的情况,即try- catch- finallytry- finally块完成它并指定完成前finally必须执行。

try {
  CODE inside the try block
}
finally {
  FIN code inside finally block
}
NEXT code executed after the try-finally block (may be in a different method).

JLS不保证在CODE之后执行FIN。JLS保证,如果执行CODENEXT,则FIN将始终在CODE之后和NEXT之前执行。

为什么JLS不保证该finally块总是在该try块之后执行?因为这是不可能的。在完成try块之后但在执行块之前,JVM不太可能会中止(终止,崩溃,关闭电源)finally。JLS无法采取任何措施来避免这种情况。

因此,任何对软件的正确行为依赖于finally在其try块完成后始终执行的块的软件都会受到错误攻击。

return在指令try块无关这个问题。如果执行后到达的代码try- catch- finally它保证了finally块将之前已经执行,有无return内部指令try块。


8

是的,它会的。不管您在try或catch块中发生了什么,除非调用System.exit()或JVM崩溃。如果块中有任何return语句,最后将在该return语句之前执行。



8

除了其他答案外,添加到@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开始,这不是输出。
山姆

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从来不会被调用

因此控制台输出不会print String:之后wait()finally

同意@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崩溃?

情况6:如果finally要由守护程序执行block ThreadThreads之前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
请参阅已接受的答案。这只是“无限循环”的极端情况。
斯蒂芬C,

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.
    }
}

sb.setLength(0)最后不应该吗?
user7294900

sb.setLength(0)只会清空StringBuffer中的数据。因此,sb = null会将对象与引用解除关联。
山姆

不应在输出中两次打印“ xyz”吗?由于该函数被调用了两次,为什么“最终”只有一次?
fresko

2
这不是一个好习惯。最后的障碍sb = null;只是添加了不需要的代码。我了解您的意思是,finally块是释放诸如数据库连接之类的资源的好地方,但是请记住,您的示例可能会使新来者感到困惑。
Roc Boronat

1
@Samim谢谢,我添加了这些行 System.out.println("---AGAIN2---"); System.out.println(sb);,现在更加清晰了。照原样,输出与您的论点背道而驰:p我也添加到您的答案中,但是编辑必须被主持人或类似人员接受。另外,您可以添加它们
fresko '19

7

实际上,在任何语言中都是如此……最终总是在return语句之前执行,无论该return在方法主体中的何处。如果不是这种情况,那么finally块将没有太大的意义。


7

除了最后要替换try块中的return的return以外,异常也是如此。引发异常的finally块将替换try块中引发的返回或异常。


7

finally 将执行,这是肯定的。

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

情况1 :

执行时System.exit()

情况2:

当您的JVM /线程崩溃时。

情况3:

在手动之间停止执行时。


6
  1. 最后,块总是被执行。除非并且直到 System.exit()语句存在(finally块中的第一个语句)。
  2. 如果system.exit()是first语句,则不会执行finally块,并且控制会从finally块中取出。每当System.exit()语句进入finally块,直到该语句finally块被执行,并且当System.exit()出现时,控制力就会完全从finally块中消失。

1
这已经被回答了很多次,那么您的答案会添加哪些新信息?
汤姆(Tom)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.