是否 finally 块总是用 Java 执行?

考虑到这段代码,我能绝对确定无论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");
}

答案

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

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

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

示例代码:

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

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

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

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

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

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

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

  • 如果try块的执行正常完成,则 [...]
  • 如果由于throwV导致try块的执行突然完成,[...]
  • 如果try块的执行由于任何其他原因R突然完成,则将执行finally块。然后有一个选择:
    • 如果 finally 块正常完成,则try语句由于原因R突然完成。
    • 如果finally块由于S突然完成,则try语句由于S突然完成( 并且R被丢弃 )。

实际上, return的说明使这一点很明确:

JLS 14.17 返回声明

ReturnStatement:
     return Expression(opt) ;

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

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

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

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

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

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

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

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

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

无论 try 块中发生了什么, Finally 都会被调用( 除非您调用System.exit(int)或 Java 虚拟机由于其他原因而退出)。

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

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

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