ChatGPT解决这个技术问题 Extra ChatGPT

finally 块是否总是在 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");
}
如果不是,则应将关键字命名为 probably
@BinoyBabu,终结器 != finally; finalizer == finalize() 方法。
@Boann 正确,“并不总是”确实。但是你永远不能使用“保证”或“总是”这样的词。

A
Ali Dehghani

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

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

如果调用 System.exit() 如果调用 Runtime.getRuntime().halt(exitStatus) 如果 JVM 先崩溃 如果 JVM 在 try 或catch 块 如果 OS 强制终止 JVM 进程;例如,在 UNIX 上 kill -9 如果主机系统死机;例如,电源故障、硬件错误、操作系统恐慌等如果 finally 块将由守护线程执行并且所有其他非守护线程在 finally 被调用之前退出


实际上 thread.stop() 并不一定会阻止 finally 块被执行。
不如说finally 块将在try之后 被调用,而before 控制权传递给以下语句。这与涉及无限循环的 try 块一致,因此 finally 块实际上从未被调用。
还有另一种情况,当我们使用嵌套的 try-catch-finally 块时
此外,如果守护线程抛出异常,也不会调用 finally 块。
@BinoyBabu - 这是关于终结器,而不是 finally 块
f
fareed

示例代码:

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

仅供参考:在 C# 中,除了不允许将 finally 子句中的语句替换为 return 2; 之外,行为是相同的(编译器错误)。
以下是需要注意的重要细节:stackoverflow.com/a/20363941/2684342
您甚至可以在 finally 块本身中添加一个 return 语句,然后它将覆盖先前的返回值。这也神奇地丢弃了未处理的异常。此时,您应该考虑重构您的代码。
这并不能真正证明最终王牌回归。返回值是从调用者代码中打印出来的。似乎证明不了多少。
对不起,但这是一个演示而不是证明。如果您可以证明此示例在所有 Java 平台上总是以这种方式运行,并且类似的示例也总是以这种方式运行,那么这只是一个证明。
M
MooBob42

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

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

从 finally 块中抛出异常也是如此。


这是一个非常糟糕的做法。请参阅 stackoverflow.com/questions/48088/…,了解有关它为什么不好的更多信息。
同意。 finally{} 中的返回会忽略 try{} 中抛出的任何异常。害怕!
@dominicbri7 为什么你认为这是一个更好的做法?当函数/方法无效时,为什么会有所不同?
出于同样的原因,我从不在我的 C++ 代码中使用 goto。我认为多次返回会使阅读变得更加困难并且更难以调试(当然在非常简单的情况下它并不适用)。我想这只是个人喜好,最后你可以使用任何一种方法来实现同样的事情
当某种特殊情况发生时,我倾向于使用多个回报。比如如果(有理由不继续)返回;
K
Kenster

这是 Java 语言规范中的官方用语。

14.20.2. Execution of try-finally and try-catch-finally

通过首先执行 try 块来执行带有 finally 块的 try 语句。然后有一个选择:如果 try 块的执行正常完成,[...] 如果 try 块的执行由于抛出值 V 而突然完成,[...] 如果 try 块的执行突然完成由于任何其他原因 R,然后执行 finally 块。然后有一个选择:如果 finally 块正常完成,那么 try 语句由于原因 R 突然完成。如果 finally 块由于原因 S 突然完成,那么 try 语句由于原因 S 突然完成(并且原因 R 被丢弃)。

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

JLS 14.17 The return Statement

ReturnStatement: return Expression(opt) ;没有 Expression 的 return 语句尝试将控制权转移给包含它的方法或构造函数的调用者。带有 Expression 的 return 语句试图将控制权转移给包含它的方法的调用者;表达式的值成为方法调用的值。前面的描述说的是“尝试转移控制”,而不仅仅是“转移控制”,因为如果方法或构造函数中有任何 try 语句,其 try 块包含 return 语句,那么这些 try 语句的任何 finally 子句都将被执行,在在将控制权转移到方法或构造函数的调用者之前,按从内到外的顺序。 finally 子句的突然完成可能会中断由 return 语句启动的控制转移。


M
MC Emperor

除了其他响应之外,重要的是要指出“终于”有权覆盖 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();
    }
}

应该注意的是,中间情况正是在 finally 块中包含 return 语句绝对可怕的原因(它可以隐藏任何 Throwable)。
想要一个被压制的OutOfMemoryError? ;)
我对其进行了测试,它确实抑制了这样的错误(耶!)。当我编译它时它也会生成一个警告(耶!)。您可以通过定义一个返回变量然后在 finally 块之后使用 return retVal after 来解决它,尽管这当然假设您抑制了一些其他异常,因为否则代码将没有意义.
J
Jared Rummler

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

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 中调试此代码,那么您会感觉到在执行 finally 块的 System.out 之后再次执行 try 块的 return 语句。但这种情况并非如此。它只是返回值 2。


这个例子很棒,它添加了几十个最终相关线程中没有提到的东西。我认为几乎没有任何开发人员会知道这一点。
如果 i 不是一个原始对象,而是一个 Integer 对象怎么办。
我很难理解这个案子。 docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.17 说,“带有 Expression 的 return 语句试图将控制权转移给包含它的方法或 lambda 主体的调用者......如果 Expression 的评估正常完成,则产生一个值 V..”可以从这个语句中猜到 - 似乎 return 不会在评估值 V 后再次评估表达式,这就是为什么更改 i 不会影响返回值,纠正我。
但是我找不到任何关于这一点的证据,它在哪里提到 return 不会再次评估表达式。
@meexplorer 有点晚了,但在 JLS 14.20.2. Execution of try-finally and try-catch-finally 中有解释 - 措辞有点复杂,还必须阅读 14.17. The return Statement
C
Community

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

输出:

X
finally trumps return... sort of
0

重要的是要知道。
很高兴知道,也很有意义。似乎实际返回 return 的值是 finally 之后的内容。计算返回值(此处为 printX())仍然在它之前。
没有。上面的代码应将 System.out.println("finally trumps return... sort of"); 替换为 System.out.print("finally trumps return in try"); return 42;
这个答案在 IMO 中是完全不言而喻的,因为 return 不会返回一些只有在调用者打印它或其他什么时才会被评估的魔法延续。 printX()return 发生之前被调用,无论任何 try/catch 或其他任何内容。
C
Chris Cooper

这就是 finally 块的全部想法。它可以让你确保你做的清理工作可能会因为你返回而被跳过,当然,除其他外。

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


这是一个极其微弱的答案。 stackoverflow.com/a/65049/715269
G
Garth Gilmour

考虑这个问题的一个合乎逻辑的方法是:

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


这只是“一种合乎逻辑的思考方式”还是 finally 块实际上是如何根据规范工作的?一个指向 Sun 资源的链接在这里会非常有趣。
s
shyam

finally 总是被执行,除非有异常的程序终止(比如调用 System.exit(0)..)。所以,你的系统输出将被打印出来


R
Ram

不,并非总是一种例外情况是// 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() 的原因之一......
J
James A. N. Stauffer

finally 中的 return 也会抛出任何异常。 http://jamesjava.blogspot.com/2006/03/dont-return-in-finally-clause.html


Y
Youcef Laidani

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

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


M
Motti

finally 总是 run 这就是重点,仅仅因为它出现在 return 之后的代码中并不意味着它就是这样实现的。 Java 运行时有责任在退出 try 块时运行此代码。

例如,如果您有以下情况:

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

运行时将生成如下内容:

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

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


M
Mendelt

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


J
Jay Riggs

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


b
bikz05

简而言之,在官方 Java 文档(单击 here)中,写到 -

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


A
Ankush soni

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

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

M
Meet Vora

答案很简单,是的。

输入:

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

答案很简单,NO。
@ChristopheRoussy 怎么样?你能解释一下吗?
阅读接受的答案,最初的问题是关于“它会一直执行吗”,但不会总是。在您的情况下,它会但这并不能回答原始问题,甚至可能会误导初学者。
那么在这种情况下它不会被执行?
在其他答案中提到的所有情况下,请参阅 1000+ 赞成票的已接受答案。
A
Anonymous Coward

不总是

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 在 CODE 之后执行。 JLS 保证如果执行 CODE 和 NEXT,则 FIN 将始终在 CODE 之后和 NEXT 之前执行。

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

因此,任何依赖于 finally 块的正确行为的软件总是在其 try 块完成后执行,这些软件都存在错误。

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


我很抱歉对这么老的答案发表评论,但我认为说 any software which for their proper behaviour depends on finally blocks always being executed after their try blocks complete are bugged. 是不正确的。 finally 块通常用于资源清理以防止泄漏。否则你会怎么做?同样,您也不能(或不应该...)捕获 Error,因为(通常)普通应用程序没有合理的方法来处理它们。
@user991710 如果 finally 块中的清理是关于关闭打开文件的示例,那么不执行 finally 块不是问题,操作系统会在程序终止时将其关闭。如果清理是删除一个有问题的文件,那么当程序最终中止时操作系统不会这样做。如果系统的另一部分依赖于总是执行此类删除的 java 程序,那么系统通常,特别是 java 程序都会被窃听。你不能依赖 finally 总是执行。如果 NEXT 已经执行,你只能依赖 finally 已经执行的保证
有趣的是,尽管据我所知它会强制您设计的某些部分。扩展您的删除示例:您是否会运行一种负责删除(清理)文件的“收割机”任务?我承认,根据您的解释,使用 finally 可能(如果有足够的时间,可能)导致错误,但是额外的开发(和测试)时间是否值得潜在的不明显的稳定性改进?更笼统地说:对特定设计的关注程度是否取决于所讨论的子系统?毕竟,时间是宝贵的资源。 :)
@user991710 使用 finally 没有任何问题。您只需要知道它做什么和不做什么。对于删除文件的特殊情况,我实际上会在 finally 块中执行此操作。但是意识到这样做并不能保证(即使 try 块的最后一行完成执行也不行)我会采取预防措施来处理这种情况。因此,如果这是一个将自动重新运行的服务式程序,则删除以前的剩余文件作为执行程序的第一步。如果系统在这种情况下发生故障是无关紧要的,那么,当然,不要打扰
s
srk

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 x 来自 foo() 的值 = 2


很好的说明性示例,+1。
K
Karthikeyan

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


a
abhig

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


G
Gautam Viradiya

是的,finally 块总是执行。大多数开发人员使用这个块来关闭数据库连接、结果集对象、语句对象,并且还使用 java hibernate 来回滚事务。


L
L Joey

finally 将执行,这是肯定的。

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

情况1 :

当您执行 System.exit() 时。

案例2:

当您的 JVM / 线程崩溃时。

案例3:

当您手动停止执行时。


N
Neuron

添加到 @vibhash's answer 因为没有其他答案可以解释在像下面这样的可变对象的情况下会发生什么。

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

将输出

某人更新


从 Java 1.8.162 开始,这不是输出。
l
lue

我试过这个,它是单线程的。

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,上面的例子是第三种情况提到here之一:

在以下代码中添加更多这样的无限循环可能性:

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

参考:How do you crash a JVM?

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

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”,这意味着“daemon thread”中的“Finally block”没有执行

主线程打印线程:Thread[My Daemon Thread,5,main]:state:BLOCKED 线程:Thread[main,5,main]:state:RUNNABLE 线程:Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE主线程启动的线程数:3 ------------------------------ -------- Daemon Thread 打印 Thread :Thread[My Daemon Thread,5,main]:state:RUNNABLE Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE 主线程启动的线程数: 2 -------------------------------------------------进程以退出代码 0 结束


请参阅已接受的答案。这只是“无限循环”的边缘情况。
s
sam

考虑以下程序:

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)吗?
sb.setLength(0) 只会清空 StringBuffer 中的数据。因此, sb = null 将解除对象与引用的关联。
“xyz”不应该在输出中打印两次吗?既然函数被调用了两次,为什么“finally”只有一次呢?
这不是一个好习惯。带有 sb = null; 的 finally 块只是添加了不需要的代码。我理解您的意思是 finally 块是释放数据库连接等资源的好地方,但请记住,您的示例可能会使新手感到困惑。
@Samim 谢谢,我添加了 System.out.println("---AGAIN2---"); System.out.println(sb); 行,现在更清楚了。事实上,输出与您的论文不符:p 我也添加到您的答案中,但编辑必须由主持人或类似的人接受。否则你可以添加它们
S
Scott Dorman

这实际上在任何语言中都是正确的……finally 总是在 return 语句之前执行,无论 return 在方法体中的什么位置。如果不是这样,finally 块就没有多大意义。


A
Alex Miller

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