为什么更改finally块中的返回variables不会改变返回值?
我有一个简单的Java类,如下所示:
public class Test { private String s; public String foo() { try { s = "dev"; return s; } finally { s = "override variable s"; System.out.println("Entry in finally Block"); } } public static void main(String[] xyz) { Test obj = new Test(); System.out.println(obj.foo()); } }
而这个代码的输出是这样的:
Entry in finally Block dev
为什么在finally块中不会覆盖s ,但控制打印输出?
try块完成后执行return语句, return语句执行时的s值是方法返回的值。 finally子句后来更改s的值(在return语句完成之后)不会(在这一点上)更改返回值。
请注意,上述处理在finally块中s本身的值的更改,而不是引用的对象。 如果s是一个可变对象的引用(该String不是),并且对象的内容在finally块中被改变,那么这些改变将在返回的值中看到。
所有这些操作的详细规则可以在Java语言规范的第14.20.2节中find。 请注意,执行return语句将被视为try块的突然终止(“ 如果尝试块的执行由于任何其他原因而突然完成,则开始”一节)。 请参阅JLS的14.17节,了解为什么return语句是块的突然终止。
通过进一步的细节:如果try-finally语句的try块和finally块由于return语句而突然终止,则来自§14.20.2的以下规则适用:
如果
try块的执行由于任何其他原因而突然完成[除了抛出exception],那么finally块将被执行,然后有一个select:
- 如果
finally块正常完成,那么由于原因R,try语句突然完成。- 如果
finally块由于S原因而突然完成,那么try语句因为原因S突然完成(并且原因R被丢弃)。
结果是finally语句块中的return语句决定了整个try-finally语句的返回值,而try语句块返回的值被丢弃。 try-catch-finally语句中会出现类似的情况,如果try块引发exception,被catch块catch ,并且catch块和finally块都有return语句。
因为在最后调用之前将返回值放在堆栈上。
如果我们看一下字节码,我们会注意到JDK已经做了一个重要的优化,而foo()方法看起来像这样:
String tmp = null; try { s = "dev" tmp = s; s = "override variable s"; return tmp; } catch (RuntimeException e){ s = "override variable s"; throw e; }
和字节码:
0: ldc #7; //loading String "dev" 2: putstatic #8; //storing it to a static variable 5: getstatic #8; //loading "dev" from a static variable 8: astore_0 //storing "dev" to a temp variable 9: ldc #9; //loading String "override variable s" 11: putstatic #8; //setting a static variable 14: aload_0 //loading a temp avariable 15: areturn //returning it 16: astore_1 17: ldc #9; //loading String "override variable s" 19: putstatic #8; //setting a static variable 22: aload_1 23: athrow
java在返回之前保存了“dev”string。 其实这里根本就不是最后一块。
这里有两件值得注意的事情:
- string是不可改变的。 当你设置s为“覆盖variabless”时,你将s设置为引用内联string,而不是改变s对象的固有char缓冲区以改变为“覆盖variabless”。
- 你把一个对栈的引用返回到调用代码。 之后(当finally块运行时),改变引用不应该为已经在堆栈上的返回值做任何事情。
我改了一下你的代码来certificate特德的观点。
正如你在输出中看到的那样,确实已经改变了,但是在返回之后。
public class Test { public String s; public String foo() { try { s = "dev"; return s; } finally { s = "override variable s"; System.out.println("Entry in finally Block"); } } public static void main(String[] xyz) { Test obj = new Test(); System.out.println(obj.foo()); System.out.println(obj.s); } }
输出:
Entry in finally Block dev override variable s
从技术上讲,如果定义了finally块,那么try块中的return不会被忽略,只有在finally块中还包含return 。
这是一个可疑的devise决策,可能是一个回顾过程中的错误(很像引用是默认可空/可变的,根据一些,检查exception)。 在许多方面,这种行为与对finally意义的口语理解是完全一致的 – “不pipe事先在try块中发生了什么,总是运行这个代码”。 因此,如果你从finally块返回true,总体效果必须总是return s ,否?
一般来说,这是一个很好的习惯用法,你应该使用finally块来清理/closures资源,但是很less从它们那里返回一个值。
试试这个:如果你想打印s的覆盖值。
finally { s = "override variable s"; System.out.println("Entry in finally Block"); return s; }