web-dev-qa-db-ja.com

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がオーバーライドされないのに、印刷出力を制御するのはなぜですか?

146
Devendra

tryブロックはreturnステートメントの実行で完了し、sステートメントの実行時のreturnの値はメソッドによって返される値です。 finally節が後でsの値を(returnステートメントの完了後)変更しても、戻り値は(その時点で)変更されません。

上記は、sが参照するオブジェクトではなく、finallyブロック内のs自体の値の変更を処理することに注意してください。 sが可変オブジェクト(Stringではない)への参照であり、オブジェクトのcontentsfinallyブロックで変更された場合、それらの変更は戻り値に表示されます。

これがどのように動作するかの詳細なルールは Java Language Specification のセクション14.20.2にあります。returnステートメントの実行は突然の終了としてカウントされることに注意してください。 tryブロックの(「で始まるセクションは、他の理由でtryブロックの実行が突然完了した場合R ....」が適用されます。)-を参照してください。 JLSのセクション14.17returnステートメントがブロックの突然の終了である理由。

さらに詳しく言うと、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ブロックでキャッチされ、catchブロックとfinallyブロックの両方にreturnステートメントがある場合、try-catch-finallyステートメントでも同様のことが起こります。

166
Ted Hopp

最終的な呼び出しの前に戻り値がスタックに配置されるためです。

64
Tordek

バイトコードの内部を見ると、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」文字列が変更されないようにしました。実際、ここには最終的なブロックはまったくありません。

33
Mikhail

ここで注目すべき2つのことがあります。

  • 文字列は不変です。 sを "変数sのオーバーライド"に設定する場合、sオブジェクトの固有のcharバッファーを "変数sのオーバーライド"に変更するのではなく、インライン化された文字列を参照するようにsを設定します。
  • 呼び出し元のコードに戻るには、スタック上のsへの参照を配置します。その後(finallyブロックが実行されるとき)、参照を変更しても、既にスタックにある戻り値に対して何も実行されません。
22
0xCAFEBABE

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
13
Frank

技術的には、returnブロックが定義されている場合、そのfinallyブロックにfinallyも含まれている場合にのみ、tryブロックのreturnは無視されません。

これは疑わしい設計上の決定であり、おそらく振り返ってみると間違いでした(参照が既定でnull可能/可変であり、一部の人はチェック済みの例外に似ている)。多くの点で、この動作はfinallyが何を意味するかについての口語的な理解と完全に一致しています-「tryブロックで事前に何が起こっても、常にこのコードを実行します」。したがって、finallyブロックからtrueを返す場合、全体的な効果は常にreturn s、いや?

一般に、これはめったに良いイディオムではありません。リソースをクリーンアップ/クローズするためにfinallyブロックを自由に使用する必要がありますが、それらから値を返すことはめったにありません。

6
Kiran Jujare

これを試してください:sのオーバーライド値を印刷する場合。

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