try/catch/finally的输出结果惊人?

6

我运行了这段代码:

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

private static int catcher() {
    try {
        System.out.println("TRY");
        thrower();
        return 1;
    } catch (Exception e) {
        System.out.println("CATCH");
        return 2;
    } finally {
        System.out.println("FINALLY");
        return 3;
    }
}

private static void thrower() {
    throw new RuntimeException();
}

我希望看到输出结果如下:

TRY
CATCH
FINALLY
2

但是出乎意料的是,输出结果为:
TRY
CATCH
FINALLY
3

我有些疑惑。 return 2 这个语句该放在哪里?finally 中使用 return 是一种不好的编程习惯吗?


无论如何,finally块总是会被调用(如果没有发生崩溃)。 - Maroun
5
在我点击你的问题之前,仅仅看了标题,我就知道我会在你的finally块中找到return。就这样。 - Marko Topolnik
无论try或catch块中发生了什么,finally块始终会被执行。 - slavko.mandic
@MarkoTopolnik 我也是 :) - Rohit Jain
9个回答

4

return 2语句去了哪里?

它消失了。

具体而言,JLS所说的是

如果catch块因原因R而突然完成,则将执行finally块。然后有一个选择:

  • 如果finally块正常完成,则try语句因原因R而突然完成。

  • 如果finally块因原因S而突然完成,则try语句因原因S而突然完成(原因R被丢弃)。

return语句是突然完成的原因之一。)

在finally中使用return是一种不好的做法吗?

基本上是这样的。因为没有充分的理由这样做,而且会吞噬异常。

例如,请观察以下内容:

static void eatAnException() {
    try {
        throw new RuntimeException("oops");
    } finally {
        return;
    }
}

在finally中的return语句会丢弃异常。相反,应该在finally块后面放置一个return语句。

2

这并不奇怪。

这是实际行为。返回值由 finally 块决定。

如果在 finally 中没有返回任何东西,那么将会返回之前要返回的值。

您可以从字节码中看到,如果您在 finally 中返回内容,则返回值将在其中被更新。

附带说明:

finally 块 是用于特殊目的的。

finally 不仅适用于异常处理——它还允许程序员避免清理代码被 return、continue 或 break 绕过的情况。将清理代码放入 finally 块中是一种良好的编程习惯,即使不预计发生异常。

不建议在其中编写逻辑。


1

Oracle文档(http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html)指出:

"当try块退出时,finally块总是会执行。这确保了即使发生意外异常,finally块也将被执行。但finally不仅仅用于异常处理 - 它允许程序员避免清理代码被返回、继续或中断绕过。把清理代码放在finally块中始终是一个好习惯,即使没有预期的异常."


1

finally块中返回的任何内容都将覆盖try/catch块内部的任何异常或返回值。

在一些特殊情况下,finally块不会被调用,如首先调用System.exit()或JVM崩溃。

这就是为什么在finally块中使用return语句被认为是一个非常糟糕的想法。


1
在JSE7语言规范§14.1中,return语句被定义为突然终止。如果您的finally块因任何原因(包括return)突然终止,则try块也会以同样的原因(如§14.20.2中所定义)结束: §14.1 [...] 突然完成总是有一个关联原因,该原因是以下之一:[...] 不带值的返回[...] 带有给定值的返回[...] §14.20.2 [...] 如果finally块因原因S而突然停止,则try语句也因原因S(并且忽略了原因R)而突然停止[...](原因R是catch的结果)。

0
编译器会在所有分支中的返回语句之前(通常作为子程序调用)插入“finally”部分(即“当try块退出时”)。这意味着您的方法将被转换为以下形式:
private static int catcher() {
    try {
        System.out.println("TRY");
        thrower();

        // -- finally section --
        System.out.println("FINALLY");
        return 3;
        // ---------------------

        return 1; // will be eliminated by the compiler
    } catch (Exception e) {
        System.out.println("CATCH");

        // -- finally section --
        System.out.println("FINALLY");
        return 3;
        // ---------------------

        return 2; // will be eliminated by the compiler
    }
}

这就是为什么你会看到:

TRY CATCH FINALLY 3

在 finally 中使用 return 是一种不好的实践吗?

理论上是的。如果你理解它的工作原理,你可以在 "finally" 中使用 "return" :) 但最好只使用 "finally" 来释放资源。

还有一个有趣的例子:

private static int catcher2() {
    int v = 1;
    try {
        System.out.println("TRY");
        thrower();
        return v;
    } catch (Exception e) {
        System.out.println("CATCH");
        v = 2;
        return v;
    } finally {
        System.out.println("FINALLY");
        v = 3;
    }
}

它返回

尝试 捕获 最后 2

因为在字节码中,在返回之前的最后一个操作(ireturn /“当try块退出时”)是iload(加载保存在堆栈中的值,此时为“2”)


0

这种行为的原因是finally总是被执行,所以当finally块中有return时,你不能在catch块中结束方法执行。

所以实际上发生的步骤是:

  1. TRY进入输出
  2. 执行thrower()方法
  3. 它抛出new RuntimeException()
  4. catcher()中的catch块捕获它
  5. CATCH进入输出
  6. 执行finally
  7. FINALLY进入输出
  8. 方法返回3 - 你不能回到catch

0
无论是否抛出异常,finally块都会被调用。在catch块之后,流程将2返回给finally块。然后finally块再将3返回给主程序。
此外,您期望打印“finally”。紧接在finally后面的return语句是3。那么您如何期望返回2呢?

0

如果 finally 块执行控制转移语句,例如 return 或带标签的 break,则在 finally 块中由 return 语句返回的值将覆盖 try 块或 catch 块中由 return 语句返回的任何值。


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