有一个代码:
private static int f (){
try {
return 1;
}
finally {
return 2;
}
}
public static final void main(String[] args) {
System.out.println(f());
}
OUTPUT: 2
似乎不言而喻,编译器命中后return 1应该结束方法并输出一个,因为方法是逐行执行的。但是编译器的行为有点奇怪,而不是返回一个,它属于该部分finally.
问题:编译器如何真正“看到”代码?也许他认为这个街区try是来自街区的呼唤finally?像这样:
int finallyCompilator(){
int tryCompilator();
return 2; //Здесь код метода finally
}
tryCompilator(){
return 1; //Здесь код метода try
}
但即便如此,编译器将如何“看到”catch 块(如果有的话)还不清楚?
一切都简单多了,
return块正在被替换为了在实践中看到这一点,让我们编译你的代码
bytecode并打开它,例如,通过Intellij IDEA并看到下图:我们可以注意到,在编译块时
returnfromtry被替换为returnfromfinally升级版:
让我们让情况更有趣,编译一个这样的函数
它
bytecode会是这样的:在这里我们看到他
return只是简单地离开了块try,System.exit(0)自然他会比块工作更快地被抛出应用程序finally。简短的回答:
finally编译器将之前的块复制return到每个块catch中。更多详情如下。为什么 finally 应该被调用
return的问题在问题中得到了回答:如果 try 返回,是否最终执行?在实现方面,除了@Komdosh 的研究,我尝试在Java 虚拟机规范中寻找finally 编译要求。第 3.13 章描述了如何使用 JSR 和 RET 指令进行最终编译。说明和实现示例已经过时,但是,从描述中,您可以大致了解标准的 finally 编译机制:
因此,try-catch-finally 块中 finally 的解释应该等同于以下内容:
catch(如果有)存储在局部变量中,跳转/跳转(JSR)到 finally 块,然后抛出异常;该指令
JSR以前用于保存 finally 块的指令。但自版本 51 (Java 7) 以来的类文件不支持该指令。JSR 文档说该指令在版本 6 之前用于支持finally从某个版本开始,Sun 编译器,然后是 Oracle,不再使用跳转,而是开始复制和嵌入 finally 块,从而消除了对指令的需要。
鉴于上述情况,以下代码块:
,编译器将转换为等效于以下内容的块:
检查 javac 为 Oracle JDK 8 生成的字节码确认 finally 块被复制了多次(
invokevirtual #10- 仅在 finally 块中调用的方法,完整代码):作为优化的结果,编译器可以减少一些指令,但方法应该不会发生显着变化。
再次值得注意的是,我在规范中没有发现编译器必须为 finally 生成的字节码有严格的要求。据我了解,编译器的另一个实现可以为此使用 goto 指令,尽管增益可以忽略不计。在现代 Java 版本中,finally 在字节码级别没有特殊结构。
相关链接:
finally 块总是被执行。例外
“也许它将 try 块视为来自 finally 块的调用”?不,只是在try之后执行finally,除非发生异常。如果它发生了,然后抓住,然后最后。