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
あなたは、finallyブロックにreturnステートメントを配置し、私と同じように繰り返し、finallyブロックは常に実行されます
フアンアントニオゴメスモリアーノ

1
tryブロックではsを返します
Devendra 2013

6
ステートメントの順序は重要です。s値を変更する前に戻ります。
Peter Lawrey、2013

6
ただし、C#とは異なりブロックでは新しい値を返すことができますfinally(C#ではできません)
Alvin Wong

回答:


167

try実行とブロック完了returnの文との価値s時にreturn文の実行には、メソッドから返された値です。finally文節が後でsreturnステートメントの完了後に)値を変更するという事実は、(その時点で)戻り値を変更しません。

上記は、参照するオブジェクトではなくsfinallyブロック内の自身の値の変更を扱っていることに注意してsください。場合s変更可能なオブジェクトへの参照であった(これStringはない)、およびコンテンツオブジェクトのはで変更されたfinallyブロックは、これらの変更は、返された値に見られます。

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

さらに詳しく言えば、ステートメントが原因でステートメントのtryブロックとブロックの両方が突然終了した場合、§14.20.2の次のルールが適用されます。finallytry-finallyreturn

tryブロックの実行が他の理由R(例外をスローする以外)で突然完了した場合、finallyブロックが実行され、次に選択肢があります。

  • 場合はfinallyブロックが正常に完了し、その後、tryステートメントが理由で中途完了R.
  • finallyブロックが理由Sで突然完了した場合、tryステートメントは理由Sで突然完了します(理由Rは破棄されます)。

その結果returnfinallyブロック内のステートメントがステートメント全体の戻り値を決定し、ブロックtry-finallyからの戻り値tryは破棄されます。同じようなことはして発生しtry-catch-finallyた場合のステートメントtryブロックが例外をスローし、それがでキャッチされたcatchブロック、および両方のcatchブロックとfinallyブロックが持っているreturn文を。


最終ブロックに値を追加するよりも、StringではなくStringBuilderクラスを使用すると、戻り値が変更されます。なぜですか?
Devendra 2013

6
@dev-私は私の答えの2番目の段落でそれについて議論します。説明した状況では、finallyブロックは返されるオブジェクト(StringBuilder)を変更しませんが、オブジェクトの内部を変更する可能性があります。finallyメソッドの前にブロックを実行実際に戻ります(にもかかわらずreturn文が終了している)、呼び出し元のコードが返された値を見ている前に、これらの変更が発生しそう。
Ted Hopp 2013

リストで試してみると、StringBuilderと同じように動作します。
Yogesh Prajapati 2013

1
@yogeshprajapati-うん。同じことは、(任意の変更可能な戻り値をtrueでStringBuilderListSet、広告nauseum):あなたが内容を変更する場合はfinallyブロックするときの方法が最終的に終了し、その後、それらの変更は、呼び出し元のコードに見られています。
テッドホップ2013

これはが起こるかを示し、その理由は示しません(これがJLSのどこでカバーされているかを指摘した場合に特に役立ちます)。
TJクラウダー2013年

65

なぜなら、戻り値は最終的にを呼び出す前にスタックに置かれるからです。


3
それは事実ですが、返された文字列が変更されなかった理由に関するOPの質問には対応していません。スタックに戻り値をプッシュする以上に、文字列の不変性とオブジェクトに対する参照に関係しています。
templatetypedef

両方の問題は関連しています。
Tordek 2013

2
@templatetypedefは、Stringが変更可能であっても、使用しても変更され=ません。
オーウェン

1
@Saul-Owenのポイント(正しい)は、不変性はOPのfinallyブロックが戻り値に影響を与えなかった理由とは何の関係もないということでした。(これは明確ではありませんが)templatetypedefが得ている可能性があるのは、戻り値が不変オブジェクトへの参照であるため、finallyブロック内のコードを変更しても(別のreturnステートメントを使用する以外)、メソッドから返された値。
Ted Hopp 2013

1
@templatetypedefいいえ、ありません。文字列の不変性とは何の関係もありません。他のタイプでも同じことが起こります。それは、finally-blockに入る前にreturn-expressionを評価すること、つまり、「スタックに戻り値をプッシュする」ことを行うことに関係しています。
ローン侯爵

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

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

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

1
だから私が文字列バッファを取る場合、スティングは上書きされますか?
Devendra 2013

10
@dev- 句のバッファの内容を変更する場合finally、それは呼び出しコードで確認できます。ただし、新しい文字列バッファをに割り当てた場合s、動作は現在と同じになります。
Ted Hopp 2013

はい、最終的にブロックの変更で文字列バッファに追加すると、出力に反映されます。
Devendra 2013

@ 0xCAFEBABEはまた、非常に良い答えと概念を提供します。
Devendra 2013

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

オーバーライドされた文字列が返されない理由。
Devendra 2013

2
TedとTordekは、「finallyが実行される前に戻り値がスタックに置かれる」と既に言ったように
Frank

1
これは良い追加情報ですが、それ自体では質問に答えないため、私はそれを賛成することに消極的です。
Joachim Sauer

5

技術的に言えreturnば、finallyブロックが定義されている場合、tryブロックのinは無視されませんreturn。最終的にブロックにが含まれている場合に限ります。

これは、おそらく振り返ってみると間違いである疑わしい設計上の決定です(参照がデフォルトでnull可能/変更可能であり、一部の例ではチェックされた例外と同様)。多くの点で、この動作は何を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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.