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


146

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


11
您应该将return语句放在finally块上,与我重复一下,finally块将始终执行
Juan Antonio Gomez Moriano

1
在尝试阻止它返回s
Devendra

6
语句的顺序很重要。您需要先返回,s然后再更改其值。
彼得·劳瑞

6
但是,可以finally块中返回新值,这与C#不同(您不能这样做)
Alvin Wong

Answers:


167

try与所述的执行块完成return语句和的值s在时间return语句执行是由该方法返回的值。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语句,则语句中也会发生类似的事情。


如果我使用StringBuilder类而不是String而不是在finally块中追加一些值,它将更改返回值。为什么?
Devendra

6
@dev-我将在答案的第二段中对此进行讨论。在您描述的情况下,该finally块不会更改返回的对象(StringBuilder),但可以更改该对象的内部。该finally块在方法实际返回之前执行(即使return语句已完成),因此这些更改发生在调用代码看到返回值之前。
Ted Hopp

尝试使用List,您会得到类似StringBuilder的行为。
Yogesh Prajapati

1
@yogeshprajapati-是的 同样是真正的那些易变的返回值(StringBuilderListSet,广告nauseum):如果您更改的内容finally块,那么这些变化是出现在当该方法最终退出调用代码。
泰德·霍普

说明发生什么,但没有说明原因(如果指出了JLS的覆盖范围,将特别有用)。
TJ Crowder

65

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


3
没错,但是它没有解决OP为何返回的字符串没有更改的问题。这与字符串的不变性以及与对象的引用有关,而不仅仅是将返回值压入堆栈。
templatetypedef

这两个问题有关。
Tordek

2
即使String是可变的,@templatetypedef也=不会使它突变。
Owen 2013年

1
@Saul-Owen的观点(正确)是不变性与OP的finally块为何不影响返回值无关。我认为templatetypedef可能已经得到了(尽管尚不清楚),因为返回的值是对不可变对象的引用,即使更改代码finally块中的代码(使用其他return语句除外)也不会影响方法返回的值。
Ted Hopp

1
@templatetypedef不,不是。与String不变性无关。任何其他类型也会发生相同的情况。它与在进入finally块之前评估return-expression有关,换句话说,exacfly“将返回值推入堆栈”。
罗恩侯爵,

32

如果查看字节码,我们会注意到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”字符串,使其在返回之前不会被更改。实际上,这根本不是最后的障碍。


这不是优化。这只是所需语义的一种实现。
罗恩侯爵,

22

这里有两点值得注意:

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

1
因此,如果我使用stringbuffer而不是被sting覆盖?
Devendra

10
@dev-如果您要更改子句中缓冲区的内容finally可以在调用代码中看到。但是,如果您为分配了新的字符串缓冲区s,则其行为将与现在相同。
Ted Hopp

是的,如果我在字符串缓冲区中追加最后块更改反映在输出上。
Devendra

@ 0xCAFEBABE您还给出了很好的答案和概念,非常感谢您。
Devendra

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

为什么重写的字符串不返回。
Devendra

2
正如Ted和Tordek所说的那样:“在最终执行之前,将返回值放入堆栈中”
Frank

1
尽管这是很好的附加信息,但我不愿意对其进行投票,因为它不能(单独)回答问题。
Joachim Sauer

5

从技术上讲,return如果finally定义了一个块,则try块中的the不会被忽略,只有在该finally块中也包含一个return

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

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


这是为什么?在所有其他情况下,它与评估顺序一致。
罗恩侯爵,

0

尝试以下操作:如果要打印s的替代值。

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

1
它发出警告..最终阻止无法正常完成
Devendra
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.