根据 Java 语言规范第 §14.20.2 节
带有 finally 块的 try 语句通过首先执行 try 块来执行。然后有一个选择:
- 如果 try 块的执行正常完成,则最终 块被执行,然后有一个选择:
- 如果 finally 块正常完成,则 try 语句正常完成。
- 如果 finally 块由于原因 S 而突然完成,则 try 语句由于原因 S 而突然完成
如果我正确解释它,那么在执行 try 块后最终被调用,但是这一切是如何工作的以及为什么我得到输出,
public static int TestTryFinallyBlock()
{
int i =0;
try
{
i= 10; //Perform some more operation
return i;
}
finally
{
i = 40;
}
}
public static void main( String[] args )
{
int i1 = TestTryFinallyBlock(); //Here the output was 10 not 40
}
我想知道这个东西是如何产生输出 10 的。
是当执行try块并遇到返回语句时,输出值已经被推送到堆栈,然后执行最终块
我知道首先遇到返回,然后最后块运行,因此输出为 10,但是jvm 如何解释或 try final 块如何由 jvm 处理或转换?
jvm 是使用 GOTO 部分跳转部分转到最终部分还是堆栈已经维护?
经过一番搜索并查看生成的字节码,我发现实际上没有看起来的最终块,也没有JVM生成的跳转或goto语句。
上面的代码被翻译为(如果我正确解释字节码,如果我错了,请纠正我)
public static int TestTryFinallyBlock()
{
int returnValue; //A temporary return variable
try
{
int i = 0;
i = 10;
returnValue = i;
i = 40;
return returnValue;
}
catch (RuntimeException e)
{
i = 40; //finally section code id copied here too
throw e;
}
}
注意:如果'i'
是对可变类对象的引用,并且对象的内容在 finally 块中发生了更改,那么这些更改也会反映在返回值中。
编译 最后
try-finally 语句的编译类似于 try-catch 的编译。在将控制权转移到 try 语句之外之前,无论这种转移是正常的还是突然的,因为抛出了异常,都必须首先执行 finally 子句。对于这个简单的例子:
void tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}
编译后的代码是:
Method void tryFinally()
0 aload_0 // Beginning of try block
1 invokevirtual #6 // Method Example.tryItOut()V
4 jsr 14 // Call finally block
7 return // End of try block
8 astore_1 // Beginning of handler for any throw
9 jsr 14 // Call finally block
12 aload_1 // Push thrown value
13 athrow // ...and rethrow value to the invoker
14 astore_2 // Beginning of finally block
15 aload_0 // Push this
16 invokevirtual #5 // Method Example.wrapItUp()V
19 ret 2 // Return from finally block
Exception table:
From To Target Type
0 4 8 any
控制可以通过四种方式传递到 try 语句之外:从该块的底部掉下来、返回、执行中断或继续语句或引发异常。
要了解有关javac如何解释最终块的更多信息。请参考 JLS - 3.13。编译 final
输入 return 时,该方法已准备好返回 10。 10 作为返回值在堆栈上。最后块被执行并将i
设置为 40,但该i
与返回值的位置不同。现在,如果有副作用,例如:
public static int TestTryFinallyBlock()
{
int i =0;
try
{
i= 10; //Perform some more operation
return i;
}
finally
{
i = 40;
System.out.println("local: "+i);
}
}
将打印 40 个。
这是因为 return 语句函数的方式以及它与 try with finally 语句交互的方式。
JLS 的第 §14.17 节描述了返回语句。
带有表达式的 return 语句尝试将控制权转移给包含它的方法的调用者;表达式的值将成为方法调用的值。更准确地说,执行这样的 return 语句首先计算表达式。如果表达式的计算由于某种原因突然完成,则返回语句会因此而突然完成。如果表达式的计算正常完成,生成值 V,则返回语句突然完成,原因是值为 V 的返回。
最后一句表示如果 return 语句的表达式正常计算,则 return 语句会突然完成。 在您的示例中,由于 return
语句和原因是return
值为 10(i
被计算为值 10)的原因,try
块突然终止。
由于在您的示例中,return
正在尝试使用 finally 块,因此 JLS 的第 §14.20.2 节告诉我们接下来会发生什么:
- 如果 try 块的执行由于任何其他原因突然完成 R,然后执行最终块,然后有一个选择:
- 如果 finally 块正常完成,则 try 语句由于原因 R 而突然完成。
如果 finally 块由于原因 S 而突然完成- ,则 try 语句由于原因 S 而突然完成(并且原因 R 被丢弃)。
因此,由于 try
块由于 return 语句被计算为值 10 而突然终止,并且由于 finally 块正常完成,因此该方法返回 10。
现在 i 的值是 40,但你没有得到 40,因为你在使其成为 40 之前得到了( returned
)这个值。
就像
i=10
return i
i=40
if return here,you get 40
虽然不是一个好的做法,只是为了演示。
public static int TestTryFinallyBlock()
{
int i =0;
try
{
i= 10; //Perform some more operation
}
finally
{
i = 40;
return i;
}
}
现在,您将在 i 中得到 40 的值。
*作为旁注:*永远不要编写资源清理以外的任何业务逻辑。这会扼杀可读性并导致错误。