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

时间:2013-04-16 07:10:11

标签: java try-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,而是控制打印输出?

7 个答案:

答案 0 :(得分:164)

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

请注意,上述内容涉及s块中finally本身值的更改,而不是s引用的对象的更改。如果s是对可变对象的引用(String不是)并且finally块中的对象的内容已更改,那么这些更改将在返回的值中看到。

有关如何运作的详细规则可在Section 14.20.2 of the Java Language Specification中找到。请注意,return语句的执行计为try块的突然终止(开始的部分“如果由于任何其他原因R ....执行try块突然完成/ em>“适用”。有关return语句突然终止块的原因,请参阅Section 14.17 of the JLS

进一步详细说明:如果try块和finally阻止 由于try-finally语句,return语句突然终止,因此§14.20.2中的以下规则适用:

  

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

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

结果是return块中的finally语句确定了整个try-finally语句的返回值,并且丢弃了try块中返回的值。如果try-catch-finally块抛出异常,它被try块捕获,catch块和{{1},则会在catch语句中发生类似的情况阻止finally个语句。

答案 1 :(得分:63)

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

答案 2 :(得分:33)

如果我们查看字节码内部,我们会注意到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”字符串在返回之前被更改。事实上,这根本没有最终阻止。

答案 3 :(得分:22)

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

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

答案 4 :(得分:13)

我改变你的代码以证明特德的观点。

正如您在输出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

答案 5 :(得分:6)

从技术上讲,如果定义了return块,则不会忽略try块中的finally,只有当finally块还包含return时才会被忽略。

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

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

答案 6 :(得分:0)

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

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