【问题标题】:Why does changing the returned variable in a finally block not change the return value?为什么在 finally 块中更改返回的变量不会更改返回值?
【发布时间】:2013-04-08 11:49:44
【问题描述】:

我有一个简单的 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 块中被覆盖,而是控制打印输出?

【问题讨论】:

标签: java try-finally


【解决方案1】:

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

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

所有这些操作的详细规则可以在Section 14.20.2 of the Java Language Specification 中找到。请注意,return 语句的执行算作try 块的突然终止(以“如果 try 块的执行由于任何其他原因突然完成 R....”的部分开始)适用)。请参阅 Section 14.17 of the JLS 了解为什么 return 语句是块的突然终止。

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

如果 try 块的执行由于任何其他原因突然完成 R [除了抛出异常],则执行 finally 块,然后有一个选择:

  • 如果finally 块正常完成,则try 语句由于原因R 突然完成。
  • 如果 finally 块由于原因 S 突然完成,则 try 语句由于原因 S 突然完成(并且原因 R 被丢弃)。

结果是finally块中的return语句决定了整个try-finally语句的返回值,而try块的返回值被丢弃。如果try 块抛出异常,则在try-catch-finally 语句中发生类似的事情,它被catch 块捕获,并且catch 块和finally 块都有return 语句。

【讨论】:

  • 如果我使用 StringBuilder 类而不是 String 而不是在 finally 块中附加一些值,它会更改返回值。为什么?
  • @dev - 我在回答的第二段中讨论了这一点。在您描述的情况下,finally 块不会更改返回的对象(StringBuilder),但它可以更改对象的内部结构。 finally 块在方法实际返回之前执行(即使 return 语句已经完成),因此这些更改发生在调用代码看到返回值之前。
  • 尝试使用 List,您会得到与 StringBuilder 相同的行为。
  • @yogeshprajapati - 是的。对于任何可变返回值(StringBuilderListSet、令人作呕)也是如此:如果您更改了 finally 块中的内容,那么这些更改会在调用该方法时在调用代码中看到终于退出了。
【解决方案2】:

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

【讨论】:

  • 确实如此,但它没有解决 OP 关于为什么返回的字符串没有改变的问题。这与字符串的不变性和引用与对象有关,而不是在堆栈上推送返回值。
  • @templatetypedef 即使 String 是可变的,使用 = 也不会改变它。
  • @Owen - 我认为您将变量与它们引用的内存对象混淆了。 = 不会改变字符串,它分配对不同内存对象的引用。可变性是值(垃圾收集的内存对象)的属性,而不是引用它们的变量。
  • @Saul - Owen 的观点(这是正确的)是不变性与为什么 OP 的 finally 块不影响返回值无关。我认为 templatetypedef 可能得到的(虽然这还不清楚)是因为返回的值是对不可变对象的引用,甚至更改了 finally 块中的代码(而不是使用另一个 return语句)不能影响从方法返回的值。
  • @templatetypedef 不,它没有。与 String 的不变性无关。任何其他类型也会发生同样的情况。它与在进入 finally 块之前评估返回表达式有关,换句话说,exacfly '将返回值推入堆栈'。
【解决方案3】:

如果我们查看字节码内部,我们会注意到 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 块。

【讨论】:

  • 这不是优化。这只是所需语义的简单实现。
【解决方案4】:

这里有两点值得注意:

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

【讨论】:

  • 所以如果我使用 stringbuffer 而不是 sting 将被覆盖??
  • @dev - 如果您要在 finally 子句中更改缓冲区的 content,则会在调用代码中看到。但是,如果您为 s 分配了一个新的字符串缓冲区,那么行为将与现在相同。
  • 是的,如果我在 finally 块更改中附加到字符串缓冲区中反映输出。
  • @0xCAFEBABE 你也给出了很好的答案和概念,我非常感谢你。
【解决方案5】:

我稍微修改一下你的代码来证明 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

【讨论】:

  • 为什么覆盖的字符串不返回。
  • 正如 Ted 和 Tordek 已经说过的那样“在 finally 执行之前将返回值放入堆栈”
  • 虽然这是很好的附加信息,但我不愿意支持它,因为它不能(单独)回答问题。
【解决方案6】:

从技术上讲,如果定义了 finally 块,try 块中的 return 不会被忽略,前提是该 finally 块还包含 return

这是一个可疑的设计决策,回想起来可能是一个错误(很像默认情况下引用可以为空/可变,并且根据某些情况,检查异常)。在许多方面,这种行为与对finally 含义的通俗理解完全一致——“无论try 块中事先发生了什么,始终运行此代码。”因此,如果您从 finally 块返回 true,则整体效果必须始终为 return s,不是吗?

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

【讨论】:

  • 这很可疑,为什么?它与所有其他上下文中的评估顺序一致。
【解决方案7】:

试试这个:如果你想打印 s 的覆盖值。

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

【讨论】:

  • 它给出警告.. finally 块没有正常完成
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-22
  • 2018-06-21
  • 1970-01-01
相关资源
最近更新 更多