148

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

4

7 回答 7

168

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

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

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

通过进一步的细节:如果语句的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块捕获,并且catch块和finally块都有return语句。

于 2013-04-16T07:13:40.503 回答
67

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

于 2013-04-16T07:12:59.470 回答
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”字符串不被更改。事实上,这里根本没有 finally 块。

于 2013-04-16T10:08:28.163 回答
22

这里有两点值得注意:

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

我稍微更改了您的代码以证明 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
于 2013-04-16T07:22:19.490 回答
5

从技术上讲,return如果定义了一个块,try 块中的 将不会被忽略finally,除非该 finally 块还包含一个return.

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

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

于 2013-04-16T07:23:48.043 回答
0

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

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}
于 2013-04-16T07:15:34.653 回答