try/catch/finally的惊人输出



我运行以下代码:

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语句在哪里?return at finally是一种不良做法吗?

return 2语句在哪里?

它不见了。

具体来说,JLS所说的是:

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

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

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

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

最终退货是一种糟糕的做法吗?

基本上是的。因为这样做没有充分的理由,它会吃掉例外。

例如,观察以下内容:

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

中的return语句最终会丢弃异常。相反,在finally块之后放置一个return语句。

这并不奇怪。

这是实际行为。返回在finally块中决定的值。

如果finally中没有返回任何内容,则返回值的前一个值就是返回值。

您可以从字节码中看到,如果您返回finally块,那么返回值将在那里更新。

附带说明:

finally块用于特殊目的。

finally不仅仅用于异常处理,它还允许程序员避免清理代码被返回、继续或中断意外绕过。将清理代码放在finally块中始终是一种很好的做法,即使在没有预期异常的情况下也是如此。

不建议在其中编写逻辑。

在JSE7语言规范§14.1中,return语句被定义为突然终止。如果您的finally块突然完成(您的return),则try块因相同原因结束(如§14.20.2所定义):

§14.1 […]突然完成总是有一个相关的原因,这是以下原因之一:[…]没有值的返回[…]有给定值的返回〔…〕

§14.20.2 […]如果finally块由于原因S而突然完成,那么try语句则由于原因S(原因R被丢弃)而突然完成。[…](原因R是catch的结果)。

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

"finally块总是在try块退出时执行。这确保了即使发生意外异常,finally块也能执行。但finally不仅仅对异常处理有用,它允许程序员避免清除代码被return、continue或break意外绕过。将清除代码放在finally块中总是一个很好的方法。"狄奇,即使没有任何例外。">

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

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

这就是为什么在finally块中有一个return语句被认为是一个非常糟糕的主意。

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

因此,实际发生的事情是循序渐进的:

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

无论是否抛出异常,finally块都会被调用。在这里,在捕获块之后,流程返回2以最终块。最后一个块再次将3返回到主程序。

还有更多你期望的"最终"会被打印出来。紧接在finally后面的return语句是3。那么,您希望2如何返回。

如果finally块执行控制传递语句,如return或标记的break,那么finally块中return语句返回的值将取代try块或catch块中return声明返回的任何值。

编译器在返回之前("当try块退出时")将"finally"部分(通常作为子例程的调用)插入所有分支。这意味着你的方法正在像这样转变:

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

这就是你看到的原因

尝试捕获最后3

最终退货是一种糟糕的做法吗?

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

还有一个有趣的案例:

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

最新更新