最终块总是在Java执行吗?

考虑到这段代码,我可以绝对肯定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");}
584928 次浏览

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

唯一不会调用finally的次数是:

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

除非有异常程序终止(如调用System.exit(0)…),否则始终会执行。所以,你的sysout会被打印出来

是的,它会被调用的。这就是有一个最终关键字的全部意义。如果跳出try/cat块可以跳过最后块,这与将System.out.println放在try/catch之外是一样的。

示例代码:

public static void main(String[] args) {System.out.println(Test.test());}
public static int test() {try {return 0;}finally {System.out.println("something is printed");}}

输出:

something is printed.0

这实际上在任何语言中都是正确的……无论返回在方法主体中的哪个位置,最终都将始终在返回语句之前执行。如果不是这样,最后块将没有太大的意义。

除非出现异常的程序终止,否则始终执行最后一个块,这是由JVM崩溃或调用System.exit(0)引起的。

最重要的是,任何返回的值都将覆盖在执行最终块之前返回的值,因此在使用try最终时要小心检查所有退出点。

此外,尽管这是一种糟糕的做法,但如果最后块中有一个返回语句,它将胜过常规块的任何其他返回。也就是说,以下块将返回false:

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

同样的事情,抛出异常从最后块。

思考这个问题的逻辑方法是:

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

除了关于返回的观点之外,异常也是如此。抛出异常的最终块将替换从try块中抛出的返回或异常。

我尝试了上面的例子,稍作修改-

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,因为此返回语句不会再次执行。

如果您在Eclipse中调试此代码,那么您会感觉在执行finally块的System.out后,try块的return语句再次执行。但事实并非如此。它只是返回值2。

最后是始终运行,这就是重点,仅仅因为它出现在返回后的代码中并不意味着它就是这样实现的。Java运行时有责任在退出try块时运行此代码。

例如,如果您有以下内容:

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

运行时将生成如下内容:

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

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

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

这就是最终块的全部思想。它可以让你确保你做了清理,否则可能会因为你返回而被跳过,当然,还有其他事情。

最后在try块中调用不管发生什么除非您调用System.exit(int)或Java虚拟机因其他原因退出)。

因为无论你有什么情况,最终都会被调用。你没有异常,它仍然被调用,捕获异常,它仍然被调用

除了其他响应之外,重要的是要指出“最终”有权覆盖try… cat块的任何异常/返回值。例如,以下代码返回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();}}

在正常的执行过程中考虑这一点(即没有抛出任何异常):如果方法不是“空”,那么它总是显式地返回一些东西,然而,最终总是被执行

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

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

这是来自Java语言规范的官方词汇。

14.20.2. try-最终和try-catch-最终的执行

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

  • 如果try块的执行正常完成,[…]
  • 如果try块的执行由于值Vthrow而突然完成,[…]
  • 如果try块的执行由于R的任何其他原因突然完成,则执行finally块。然后有一个选择:
    • 如果最终块正常完成,那么try语句由于原因R而突然完成。
    • 如果finally块由于原因S而突然完成,那么try语句由于原因S和原因R被丢弃)而突然完成。

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

JLS 14.17返回语句

ReturnStatement:return Expression(opt) ;

一个return语句,没有Expression尝试,用于将控制权转移到包含它的方法或构造函数的调用者。

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

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

如果抛出异常,则最终运行。如果未抛出异常,则最终运行。如果捕获了异常,则最终运行。如果未捕获异常,则最终运行。

只有当JVM退出时它才不会运行。

尝试此代码,您将了解最终块中的代码在返回语句后执行

public class TestTryCatchFinally {static int x = 0;
public static void main(String[] args){System.out.println(f1() );System.out.println(f2() );}
public static int f1(){try{x = 1;return x;}finally{x = 2;}}
public static int f2(){return x;}}

不,并不总是有一个例外情况//System.exit(0);之前的最后块防止最终被执行。

  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");}}}

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

这是对凯文的回答的阐述。重要的是要知道要返回的表达式在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");return 42;}}

输出:

Xfinally trumps return... sort of42

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

无论是否有异常句柄,最终块总是执行。如果在try块之前发生任何异常,则最终块将不执行。

简而言之,在官方Java文档(Click这里)中,它写着-

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

是的,最后块总是执行。大多数开发人员使用此块关闭数据库连接、结果集对象、语句对象,并在java休眠中使用此块来回滚事务。

finally将执行,这是肯定的。

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

案例1:

当您执行System.exit()时。

案例2:

当您的JVM/线程崩溃时。

案例3:

当您的执行在中间手动停止时。

我对不同论坛上提供的所有答案感到非常困惑,并决定最终编码并查看。输出是:

最后将被执行,即使在try和catch块中有返回。

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块中的返回被System.exit(0)替换,并且由于任何原因在它之前发生异常。

不总是这样

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不保证FIN代码之后执行。JLS保证如果代码NEXT被执行,那么FIN将始终在代码之后和NEXT之前执行。

为什么JLS不保证finally块总是在try块之后执行?因为这是不可能的。在完成try块之后但在执行finally块之前,JVM不太可能但有可能被中止(杀死、崩溃、断电)。JLS无法避免这种情况。

因此,任何为了其正确行为而依赖于finally块的软件总是在其try块完成后执行。

try块中的return指令与此问题无关。如果执行在try-catch-finally之后到达代码,则保证finally块将在try块内执行,无论是否有return指令。

我试过了,它是单线程的。

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");}}

mainThread将永远处于wait状态,因此#3永远不会被召唤

所以控制台输出不会printString:在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块将由守护进程Thread执行,并且所有其他非守护进程Threads在调用finally之前退出。

public static void main(String args[]) {Runnable runnable = new Runnable() {@Overridepublic void run() {try {printThreads("Daemon Thread printing");// just to ensure this thread will live longer than main threadThread.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("-------------------------------------------------");}

输出:这不会打印“最终”,这意味着“守护进程线程”中的“最终块”没有执行

main Thread PrintingThread :Thread[My Daemon Thread,5,main]:state:BLOCKEDThread :Thread[main,5,main]:state:RUNNABLEThread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLEThread count started by Main thread:3-------------------------------------------------Daemon Thread printingThread :Thread[My Daemon Thread,5,main]:state:RUNNABLEThread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLEThread count started by Main thread:2-------------------------------------------------
Process finished with exit code 0

如果Exception嵌套#0块中是thrown,也可以过早退出finally。编译器会警告您finally块不能正常完成,或者您有不可达代码的错误。仅当throw不在条件语句后面或循环内时,才会显示不可达代码的错误。

try{}finally{try{}finally{//if(someCondition) --> no error because of unreachable codethrow new RunTimeException();}int a = 5;//unreachable code}

与以下代码相同:

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

f将返回2!

答案很简单

输入:

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");}

输出:

catchfinally

是的,它总是会被调用,但在一种情况下,当你使用System.exit()时,它不会调用

try{//risky code}catch(Exception e){//exception handling code}finally(){//It always execute but before this block if there is any statement like System.exit(0); then this block not execute.}
  1. 最后,块总是被执行。除非和直到System.exit()语句存在于那里(最后块中的第一个语句)。
  2. 如果system.exit()是第一个语句,则最终块不会执行,并且控制来自最终块。每当System.exit()语句进入最终块,直到该语句最终块执行,当System.exit()出现时,控制力完全从最终块中出来。

如果您不处理异常,在终止程序之前,JVM执行最终块。只有当程序的正常执行将失败意味着程序由于以下原因而终止时,它才不会执行。

  1. 通过导致导致进程中止的致命错误。

  2. 程序由于内存损坏而终止。

  3. 通过调用System.exit()

  4. 如果程序进入无穷大循环。

在几个独特的场景中,在返回后不会调用最终块:如果首先调用System.exit(),或者如果JVM崩溃。

让我用最简单的方式回答你的问题。

规则1:最终块总是运行(虽然也有例外。但让我们坚持一段时间。)

控制的转移可以作为正常执行的结果发生,也可以作为执行关掉、继续、goto或返回语句的结果发生,也可以作为异常传播的结果发生。

在特定的返回语句的情况下(因为它的标题),控件必须离开调用方法,因此调用相应的try-最终结构的最终块。返回语句在最终块之后执行。

如果最终块中也有返回语句,它肯定会覆盖try块中挂起的语句,因为它清除了调用堆栈。

您可以在这里参考更好的解释:http://msdn.microsoft.com/en-us/……这个概念在所有高级语言中基本相同。

是的,因为无控制语句可以阻止finally被执行。

这是一个参考示例,其中所有代码块都将被执行:

| x | Current result | Code|---|----------------|------ - - -|   |                ||   |                | public static int finallyTest() {| 3 |                |     int x = 3;|   |                |     try {|   |                |        try {| 4 |                |             x++;| 4 | return 4       |             return x;|   |                |         } finally {| 3 |                |             x--;| 3 | throw          |             throw new RuntimeException("Ahh!");|   |                |         }|   |                |     } catch (RuntimeException e) {| 4 | return 4       |         return ++x;|   |                |     } finally {| 3 |                |         x--;|   |                |     }|   |                | }|   |                ||---|----------------|------ - - -|   | Result: 4      |

在下面的变体中,return x;将被跳过。结果仍然是4

public static int finallyTest() {int x = 3;try {try {x++;if (true) throw new RuntimeException("Ahh!");return x; // skipped} finally {x--;}} catch (RuntimeException e) {return ++x;} finally {x--;}}

引用当然会跟踪它们的状态。此示例返回value = 4的引用:

static class IntRef { public int value; }public static IntRef finallyTest() {IntRef x = new IntRef();x.value = 3;try {return x;} finally {x.value++; // will be tracked even after return}}

是的,它写着这里

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

添加到@陈志立,因为没有其他答案解释了在像下面这样的可变对象的情况下会发生什么。

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

try-catch-finally是使用异常处理案例的关键词。
正常解释

try {//code statements//exception thrown here//lines not reached if exception thrown} catch (Exception e) {//lines reached only when exception is thrown} finally {// always executed when the try block is exited//independent of an exception thrown or not}

最终块阻止执行…

  • 当你呼唤System.exit(0);
  • 如果JVM退出。
  • JVM中的错误

即使您在try块中放置了返回语句,最终块也会始终执行。最终块将在返回语句之前执行。

考虑以下程序:

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");}}}

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

最后总是叫到最后

当你尝试时,它会执行一些代码,如果在try中发生了一些事情,那么cat会捕获该异常,你可以打印一些mssg或抛出错误,然后最后执行块。

例如,如果您在Java中使用扫描仪,您可能应该关闭扫描仪,因为它会导致其他问题,例如无法打开某些文件

以下是一些可以绕过最终块的条件:

  1. 如果JVM在执行try或catch代码时退出,则最终块可能不会执行。更多关于sun教程
  2. 正常关闭-这发生在最后一个非守护线程退出时或Runtime.exit()(不错的博客)时。当线程退出时,JVM会执行正在运行的线程的清单,如果只剩下守护线程,它会启动有序关闭。当JVM停止时,任何剩余的守护线程都被放弃,最终块不执行,堆栈不解绕JVM只是退出。应该谨慎使用守护线程,很少有处理活动可以在任何时候安全地放弃,而不需要清理。特别是,将守护线程用于可能执行任何类型I/O的任务是危险的。守护线程最好保存为“内务”任务,例如定期从内存缓存中删除过期条目的后台线程(来源

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

public class TestDaemon {private static Runnable runnable = new Runnable() {@Overridepublic 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.");}}

输出:

Is aliveIs aliveIs aliveIs aliveIs aliveIs aliveIs aliveIs aliveIs aliveIs aliveLast non-daemon thread exits.Is aliveIs aliveIs aliveIs aliveIs alive

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);}}

输出:

x值最后=3
x值来自foo()=2

再利用试验示例

static class IamAutoCloseable implements AutoCloseable {private final String name;IamAutoCloseable(String name) {this.name = name;}public void close() {System.out.println(name);}}
@Testpublic void withResourceFinally() {try (IamAutoCloseable closeable1 = new IamAutoCloseable("closeable1");IamAutoCloseable closeable2 = new IamAutoCloseable("closeable2")) {System.out.println("try");} finally {System.out.println("finally");}}

测试输出:

trycloseable2closeable1finally

我很晚才在这里回答,但我很惊讶没有人提到删除堆栈帧的Java调试器选项。我是IntelliJ中此功能的重度用户。(我是当然 Eclipse,NetBeans支持相同的功能。)

如果我从一个try或catch块中删除堆栈帧,然后是最后一个块,IDE会提示我:“我应该执行最后一个块吗?”显然,这是一个人工运行时环境——调试器!

为了回答你的问题,我想说你只能保证它在附加调试器时忽略时运行,并且(像其他人所说的)方法something()不会(a)通过JNI调用Java方法System.exit(int)或(b)C函数exit(int)/abort()或(c)做一些疯狂的事情,比如调用kill -9 $PID本身(!)。

接受的答案在几乎所有方面都是正确的,但它仍然只是真相的一半(好吧,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块总是正常终止,否则它是否总是被执行可能并不重要。