为什么在 finally 块中更改返回的变量不会更改返回值?

我有一个简单的 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

为什么 s没有在 finally块中被覆盖,而是控制打印输出?

12673 次浏览

因为返回值是在调用 finally 之前放在堆栈上的。

try块完成了 return语句的执行,return语句执行时 s的值是该方法返回的值。finally子句稍后更改 s的值(在 return语句完成之后)这一事实不会(在此时)更改返回值。

请注意,上面处理的是 finally块中 s本身值的更改,而不是 s引用的对象的更改。如果 s是对一个可变对象的引用(而 String不是) ,并且对象的 内容finally块中发生了更改,那么这些更改将在返回的值中看到。

所有这些操作的详细规则可以在 Java 语言规格说明书第14.20.2节中找到。请注意,return语句的执行计算为 try块的突然终止(应用以“ 如果 try 块的执行由于任何其他原因突然完成,则..。”开始的部分)。有关 return语句是块突然终止的原因,请参阅 司法及法律协助条例第14.17条

通过进一步的细节: 如果都 try块和 finally块 因 return语句而突然终止的 try-finally语句,则适用14.20.2中的以下规则:

如果由于任何其他原因(除了抛出异常之外) ,try块的执行突然完成,那么就执行 finally块,然后有一个选择:

  • 如果 finally块正常完成,那么由于 R 的原因,try语句突然完成。
  • 如果 finally块因为原因 S 突然完成,那么 try语句因为原因 S 突然完成(因为 R 被丢弃)。

结果是,finally块中的 return语句确定整个 try-finally语句的返回值,并丢弃来自 try块的返回值。如果 try块抛出异常,它被 catch块捕获,并且 catch块和 finally块都有 return语句,那么在 try-catch-finally语句中也会发生类似的情况。

尝试这样做: 如果要打印 s 的重写值。

finally {
s = "override variable s";
System.out.println("Entry in finally Block");
return s;
}

这里有两件值得注意的事情:

  • 字符串是不可变的。当将 s 设置为“覆盖变量 s”时,将 s 设置为引用内联的 String,而不是将 s 对象的固有字符缓冲区更改为“覆盖变量 s”。
  • 您在堆栈上放置一个对 s 的引用,以返回到调用代码。然后(当 finally 块运行时) ,修改引用不应该对堆栈上已有的返回值执行任何操作。

我稍微修改了一下你的代码来证明 Ted 的观点。

正如您在输出中看到的,s确实改变了,但是在返回之后。

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时才会忽略。

这是一个可疑的设计决策,可能是回顾过去时的一个错误(很像引用在默认情况下是可空/可变的,并且,根据一些检查的异常)。在许多方面,这种行为与通俗地理解 finally的意思完全一致——“不管在 try块之前发生了什么,始终运行这段代码。”因此,如果从 finally块返回 true,总体效果必须始终是 return s,不是吗?

一般来说,这很少是一个好习惯用法,您应该随意地使用 finally块来清理/关闭资源,但是很少从它们返回值。

如果我们看一下字节码的内部,我们会注意到 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”字符串,事实上这里根本没有 finally 块。