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

3004

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

唯一不会调用finally的情况是:

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

47
实际上,thread.stop()并不一定能够阻止finally代码块的执行。 - Piotr Findeisen
215
我们可以这样说,finally块将在try块之后被调用,且在控制权传递到下一条语句之前被执行。这与try块涉及无限循环且finally块实际上从未被调用是一致的。 - Andrzej Doyle
9
还有一种情况,当我们使用嵌套的 try-catch-finally 代码块时。 - ruhungry
16
@BinoyBabu提到的是finalizer,而不是finally块。 - avmohan
8
@AmrishPandey Oracle的页面中根本没有提到守护进程(daemons)。另一个博客页面讨论了JVM是如何“终止”的,但未能展示与非守护线程之间的任何区别。事实是:在System.exit完成后(包括关闭钩子(shutdown hooks))或其他终止情况下,所有线程无论是守护线程还是非守护线程都会就地死亡:无论哪种方式,都不会执行finally代码块。当博主指出这一点时,其是错误的......你可以自己试试看。 - SusanW
显示剩余11条评论

623

示例代码:

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

22
在C#中,除了不能将finally语句块中的语句替换为return 2;(编译器错误),其它行为与上述描述相同。 - Alexander Pacha
15
这里有一个需要注意的重要细节:https://dev59.com/tnVD5IYBdhLWcg3wKoSH#20363941 - WoodenKitty
24
甚至可以在finally块中添加return语句,这将覆盖先前的返回值。这也神奇地丢弃了未处理的异常。此时,您应该考虑重构代码。 - Zyl
10
这并不真正证明“finally”胜过“return”。返回值是从调用者代码中打印的。似乎并没有证明太多东西。 - Trimtab
23
抱歉,这只是一次演示而非证明。只有当您能展示此示例在所有Java平台上始终以此方式运行,并且类似的示例也始终如此时,才算是证明。 - Stephen C
显示剩余4条评论

425

此外,虽然这是一个不良的实践方式,但如果 finally 代码块中存在 return 语句,则会覆盖常规代码块中的任何其他 return。也就是说,以下代码块将返回 false:

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

在 finally 块中抛出异常也是同样的道理。


106
这是一种非常糟糕的做法。请参考https://dev59.com/tHVD5IYBdhLWcg3wOo5h了解更多关于为什么这样做不好的信息。 - John Meagher
23
同意。在finally{}中的返回语句会忽略try{}块中抛出的任何异常。令人不安! - neu242
10
为什么您认为这是更好的实践?当函数/方法是void时,为什么应该有所不同? - corsiKa
8
出于同样的原因,我在我的C++代码中从不使用goto语句。我认为多个返回语句会使代码更难读和调试(当然,在非常简单的情况下不适用)。我想这只是个人喜好,在最终目标上,两种方法都可以实现相同的效果。 - dominicbri7
18
当发生某种异常情况时,我倾向于使用多个"return"语句。比如,如果(有理由不继续执行)就返回; - iHearGeoff
显示剩余5条评论

264

以下是Java语言规范官方的说法。

14.20.2. try-finally和try-catch-finally的执行

带有finally块的try语句的执行顺序为:首先执行try语句块,然后进行选择:

  • 如果try语句块的执行正常完成,[...]
  • 如果由于抛出值V而使try语句块的执行异常终止,[...]
  • 如果try语句块由于任何其他原因R而异常地完成,则执行finally块。随后进行选择:
    • 如果finally块的执行正常完成,则try语句以原因R异常终止。
    • 如果finally块由于原因S而异常地完成,则try语句以原因S异常终止(原因R被丢弃)。

return语句的规范实际上已经明确说明了这一点:

JLS 14.17 return语句

ReturnStatement:
     return Expression(opt) ;

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

如果一个带有Expressionreturn语句,尝试将控制权转移给包含它的方法的调用者;Expression的值成为方法调用的返回值。

上述描述使用"尝试将控制权转移"而不是只是"转移控制权",因为如果方法或构造函数中有任何try语句,其try块包含return语句,则在将控制权转移到方法或构造函数的调用者之前,将按最内层到最外层的顺序执行这些try语句的任何finally子句。finally子句的突然完成可能会干扰由return语句引发的控制转移。


174

除了其他回答之外,需要指出的是'finally'语句块有权覆盖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();
    }
}

69
需要注意的是,中间情况恰恰是在 finally 块内部放置返回语句绝对糟糕的原因(它可能隐藏任何 Throwable)。 - Dimitris Andreou
3
谁不想要一个被压制的OutOfMemoryError错误? ;) - RecursiveExceptionException
我已经测试过了,它确实可以抑制这种错误(哎呀!)。当我编译它时,它还会生成一个警告(耶!)。你可以通过定义一个返回变量,然后在finally块之后使用return retVal来解决它,尽管这当然假定你抑制了一些其他异常,否则代码将毫无意义。 - Maarten Bodewes

133

这里详细解释了Kevin's answer。需要知道的是,在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;
    }
}

输出:

X
finally trumps return... sort of
42

10
重要了解。 - Aminadav Glickshtein
很好了解,也很有道理。似乎实际返回return的值是在finally之后。计算返回值(这里是printX())仍然在其之前。 - Albert
2
不对。上面的代码应该将 System.out.println("finally trumps return... sort of"); 替换为 System.out.print("finally trumps return in try"); return 42; - Pacerier
在我看来,这个答案是非常自明的,因为 return 并没有返回某种神奇的延续,只有在调用者打印它或其他操作时才会被执行。无论有任何 try/catch 或其它情况,printX() 都会在 return 发生之前被调用。 - Christopher Schultz

130

我尝试对上述示例进行了轻微修改 -

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

以上代码输出:

finally优先于return。
2

这是因为当执行return i;时,i的值为2。在此之后,执行finally块,在其中将12赋给i,然后执行System.out

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

如果您在Eclipse中调试此代码,则会感到在执行finally块的System.out之后,try块的return语句再次执行。但实际情况并非如此,它只是简单地返回值2。


12
这个例子真棒,它提供了之前几十个相关帖子中没有提到的内容。我认为很少有开发人员会知道这个。 - HopefullyHelpful
5
如果 i 不是一个基本类型,而是一个整数对象(Integer object),会发生什么? - Yamcha
但我找不到任何关于这个的证据,在哪里提到了return不会再次评估表达式。 - the-dumb-programmer
1
@meexplorer 有点晚了,但是在JLS 14.20.2.执行try-finally和try-catch-finally中有解释 - 措辞有点复杂,还必须阅读14.17.返回语句 - user85421
@ Yamcha,它仍然是一样的。 - Ngupta
显示剩余3条评论

52

这就是finally代码块的全部意义。它可以确保你进行清理工作,否则这些工作可能会因为你返回而被跳过,当然还有其他原因。

不管try代码块中发生了什么(除非你调用了System.exit(int)或Java虚拟机由于某些其他原因退出),finally代码块都会被执行


那是一个非常薄弱的回答。https://dev59.com/tnVD5IYBdhLWcg3wKoSH#65049 - Gangnus

42

一个逻辑上的思考方式是:

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

10
这只是“一种合乎逻辑的思考方式”,还是根据规范finally块真正的工作方式?提供一个Sun资源的链接将非常有趣。 - matias

21
无论何时,只要程序没有异常终止(比如调用了 System.exit(0)),finally 块都会被执行。因此,你的 System.out 语句将会被打印出来。

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