我运行以下代码:
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
块的方法执行。
因此,实际发生的事情是循序渐进的:
TRY
进入输出- 执行
thrower()
方法 - 它抛出
new RuntimeException()
catcher()
中的catch
块捕获它CATCH
输出- 执行
finally
块 FINALLY
去输出- 该方法返回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")