以下に示すように、単純な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
がオーバーライドされないのに、印刷出力を制御するのはなぜですか?
try
ブロックはreturn
ステートメントの実行で完了し、s
ステートメントの実行時のreturn
の値はメソッドによって返される値です。 finally
節が後でs
の値を(return
ステートメントの完了後)変更しても、戻り値は(その時点で)変更されません。
上記は、s
が参照するオブジェクトではなく、finally
ブロック内のs
自体の値の変更を処理することに注意してください。 s
が可変オブジェクト(String
ではない)への参照であり、オブジェクトのcontentsがfinally
ブロックで変更された場合、それらの変更は戻り値に表示されます。
これがどのように動作するかの詳細なルールは 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
ステートメントでも同様のことが起こります。
最終的な呼び出しの前に戻り値がスタックに配置されるためです。
バイトコードの内部を見ると、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」文字列が変更されないようにしました。実際、ここには最終的なブロックはまったくありません。
ここで注目すべき2つのことがあります。
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
技術的には、return
ブロックが定義されている場合、そのfinallyブロックにfinally
も含まれている場合にのみ、tryブロックのreturn
は無視されません。
これは疑わしい設計上の決定であり、おそらく振り返ってみると間違いでした(参照が既定でnull可能/可変であり、一部の人はチェック済みの例外に似ている)。多くの点で、この動作はfinally
が何を意味するかについての口語的な理解と完全に一致しています-「try
ブロックで事前に何が起こっても、常にこのコードを実行します」。したがって、finally
ブロックからtrueを返す場合、全体的な効果は常にreturn s
、いや?
一般に、これはめったに良いイディオムではありません。リソースをクリーンアップ/クローズするためにfinally
ブロックを自由に使用する必要がありますが、それらから値を返すことはめったにありません。
これを試してください:sのオーバーライド値を印刷する場合。
finally {
s = "override variable s";
System.out.println("Entry in finally Block");
return s;
}