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

Dev*_*dra 145 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码的输出是这样的:

Entry in finally Block
dev  
Run Code Online (Sandbox Code Playgroud)

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

Ted*_*opp 166

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

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

有关所有这些操作的详细规则可以在Java语言规范的第14.20.2节中找到.请注意,return语句的执行计为try块的突然终止(开始的部分" 如果执行块因任何其他原因而突然完成R .... "适用).有关语句突然终止块的原因,请参见JLS的第14.17节return.

通过进一步的细节:如果语句的try块和finally块都try-finallyreturn语句而突然终止,那么§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语句,那么在语句中会发生类似的事情.

  • @dev - 我在答案的第二段讨论这个问题.在你描述的情况下,`finally`块不会改变返回的对象(`StringBuilder`),但它可以改变对象的内部.`finally`块在方法实际返回之前执行(即使`return`语句已经完成),因此这些更改发生在调用代码看到返回值之前. (6认同)

Tor*_*dek 64

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

  • 这是真的,但它没有解决OP关于为什么返回的字符串没有改变的问题.这与字符串不变性和引用与对象有关,而不是在栈上推送返回值. (3认同)
  • @templatetypedef 即使 String 是可变的,使用 `=` 也不会改变它。 (2认同)

Mik*_*ail 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;
}
Run Code Online (Sandbox Code Playgroud)

和字节码:

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
Run Code Online (Sandbox Code Playgroud)

java保留"dev"字符串在返回之前被更改.事实上,这根本没有最终阻止.


0xC*_*ABE 22

这里有两件事值得注意:

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

  • @dev - 如果要在`finally`子句中更改缓冲区的_content_,则可以在调用代码中看到.但是,如果你为`s`分配了一个新的stringbuffer,那么行为将与现在相同. (10认同)

Fra*_*ank 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);
}
}
Run Code Online (Sandbox Code Playgroud)

输出:

Entry in finally Block 
dev 
override variable s
Run Code Online (Sandbox Code Playgroud)

  • 正如Ted和Tordek已经说过"在最终执行之前将返回值放在堆栈上" (2认同)

小智 6

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

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

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