为什么更改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; }