JAvaにおけるtry、catch、finallyに関する詳細分析
あるブロガーの文章を見て、javaの中でtry、catch、finallyの中でいくつかの問題について説明しました.
次に例(例1)を見てjavaにおけるtry,catch,finallyの処理の流れを説明する
まずプログラムはtry文ブロックを実行し、変数tをtryに割り当て、異常が発見されなかったため、次にfinally文ブロックを実行し、変数tをfinallyに割り当て、それからreturn t、tの値はfinallyであり、最後のtの値はfinallyであり、プログラム結果はfinallyを表示すべきであるが、実際の結果はtryである.なぜそうなるのか、まずこのコードがコンパイルされたclass対応のバイトコードを見て、仮想マシン内部がどのように実行されているかを見てみましょう.
ターゲットファイル(.classファイル)のバイトコード情報をjavap-verbose TryCatchFinallyで表示します
システム運転環境:mac os lionシステム64 bit
jdk情報:Java(TM)SE Runtime Environment(build 1.6.0_29-b 11-402-11 M 3527) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)
コンパイルされたバイトコードの一部の情報は、testメソッドだけを見て、他のものは無視します.
まずLocalVariableTable情報を見て、この中で2つの変数を定義しました.1つはt Stringタイプで、1つはe Exceptionタイプです.
次はコード部分を見て
[0-2]行目は、0番目の変数に「」、すなわちString t=「」;
[3-6]行目、すなわちtry文ブロック付与文、すなわちt=「try」を実行する.
7行目、ポイントは7行目で、s番目に対応する値「try」を3番目の変数に払いますが、この中の3番目の変数は定義されていません.これはおかしいです.
行目[8-10]は、0番目の変数、すなわちt=「finally」
[11-12]行目、3番目の変数に対応する値を返す
バイトコードにより、try文のreturnブロックでは、returnが返す参照変数(tは参照タイプ)はtry文外で定義された参照変数tではなく、システムが参照tに対応する値、すなわちtryを指し、finally文で参照tが値finallyを指しても、returnの戻り参照はtではないので、tを参照する対応する値はtry文の戻り値とは無関係です.
次に例を示す:(例2)
ここでは最初のコードを少し修正しましたが、finally文ブロックにreturn tの式を加えただけです.
最初のセグメントコードの解釈に従って、try{}文を先に行い、returnの前に現在のtの値tryを変数t'に保存し、finally文ブロックを実行し、変数tの値を変更し、変数tを返す.
この中には2つのreturn文がありますが、プログラムはtryかfinallyを返します.次にバイトコード情報を見てみましょう
このコードが翻訳されたバイトコードは、最初のコードとはまったく異なり、code属性を見続けます.
[0-2]行、[3-5]行の第1セグメントのコード論理は類似しており、tを初期化し、try中のtをtryに付与する
6行目、ここで17行目にジャンプし、[17-19]はfinallyの付与文を実行し、変数tをfinallyに付与し、tに対応する値を返す
try文のreturn文が無視されることが分かった.jvmは、1つのメソッドに2つのreturn文があるのはあまり意味がないと考えているかもしれませんが、tryのreturn文は無視され、直接機能しているのはfinallyのreturn文なので、今回はfinallyを返します.
次に複雑な例を見てみましょう.(例3)
この中にtry文が投げ出されます JAva.lang.NumberFormatExceptionなので、プログラムはまずcatch文の中のロジックを実行し、tはcatchに割り当てられ、returnを実行する前に、戻り値を一時変数の中のt'に保存し、finallyのロジックを実行し、tはfinallyに割り当てられますが、戻り値とt'なので、変数tの値と戻り値はもう関係ありません.戻り値はcatchです
例4:
これは例2と少し似ていますが、try文に異常が投げ出されたため、プログラムはcatch文ブロックに移行し、catch文はreturn文を実行する前にfinallyを実行し、finally文にreturnがあればfinallyの文値を直接実行し、finallyを返します
例5:
この例ではcatch文ブロックにInteger.parser(null)文を追加し,強制的に例外を投げ出した.そしてfinally文ブロックにはreturn文はありません.引き続き分析すると、try文が異常を投げ出すため、プログラムはcatch文ブロックに入り、catch文ブロックはまた異常を投げ出し、catch文が終了することを説明するとfinally文ブロックを実行し、tに値を付与する.そしてcatch文ブロックに異常が投げ出されます.結果としてjava.lang.NumberFormatException異常を放出
例6:
この例と上記の例で唯一異なるのは、finally文にreturn文ブロックがあることです.try catchで実行される論理は、上記の例と同様に、catch文ブロックに異常が投げ込まれた後、finally文に入るのが速く、tを返します.プログラムはcatch文ブロックから投げ出された異常情報を無視し,tに対応する値であるfinallyを直接返す.方法は異常を投げ出さない
例7:
この例では、catch文のcatchはjava.lang.NumberFormatException異常ではなくNPE異常なので、catch文ブロックには入らず、finally文ブロックに直接入り、finallyがsに値を付けた後、try文からjava.lang.NumberFormatException異常を投げ出す.
例8
上記の例のtry catchの論理と同様に、try文はfinally文の実行を完了し、finallyはsを割り当て、sを返し、最後にプログラム結果はfinallyを返す
例9:
この例ではfinally文にString.valueOf(null)を追加し,NPE異常を強制放出する.まずプログラムはtry文を実行し,戻り実行ではfinally文ブロックを実行し,finally文はNPE異常を放出し,結果全体はNPE異常を返す.
以上のすべての例をまとめる
1 try、catch、finally文では、try文にreturn文がある場合、返された後の現在のtryでの変数に対応する値が返され、その後変数を変更してもtryでのreturnの戻り値には影響しません.
2 finallyブロックにreturn文がある場合、tryまたはcatchの戻り文は無視されます.
3 finallyブロックから例外が投げ出された場合、try、catch、finallyブロック全体に例外が投げ出される
したがってtry,catch,finally文ブロックで注意すべきは
1 tryまたはcatchでreturn文をできるだけ使用します.finallyブロックでtryまたはcatchの戻り値を変更することはできません.
2 finallyブロックでreturn文の使用を避ける.finallyブロックでreturn文を使用すると、try、catchブロックの異常情報が消化され、エラーの発生が遮断されるからだ.
3 finallyブロックで再び例外を放出しないでください.そうしないと、try文ブロックを含むメソッド全体が例外を放出し、try、catchブロックの例外を消化します.
次に例(例1)を見てjavaにおけるtry,catch,finallyの処理の流れを説明する
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
// result = "catch";
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
まずプログラムはtry文ブロックを実行し、変数tをtryに割り当て、異常が発見されなかったため、次にfinally文ブロックを実行し、変数tをfinallyに割り当て、それからreturn t、tの値はfinallyであり、最後のtの値はfinallyであり、プログラム結果はfinallyを表示すべきであるが、実際の結果はtryである.なぜそうなるのか、まずこのコードがコンパイルされたclass対応のバイトコードを見て、仮想マシン内部がどのように実行されているかを見てみましょう.
ターゲットファイル(.classファイル)のバイトコード情報をjavap-verbose TryCatchFinallyで表示します
システム運転環境:mac os lionシステム64 bit
jdk情報:Java(TM)SE Runtime Environment(build 1.6.0_29-b 11-402-11 M 3527) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)
コンパイルされたバイトコードの一部の情報は、testメソッドだけを見て、他のものは無視します.
public static final java.lang.String test();
Code:
Stack=1, Locals=4, Args_size=0
0: ldc #16; //String
2: astore_0
3: ldc #18; //String try
5: astore_0
6: aload_0
7: astore_3
8: ldc #20; //String finally
10: astore_0
11: aload_3
12: areturn
13: astore_1
14: ldc #22; //String catch
16: astore_0
17: aload_0
18: astore_3
19: ldc #20; //String finally
21: astore_0
22: aload_3
23: areturn
24: astore_2
25: ldc #20; //String finally
27: astore_0
28: aload_2
29: athrow
Exception table:
from to target type
8 13 Class java/lang/Exception
8 24 any
19 24 any
LineNumberTable:
line 5: 0
line 8: 3
line 9: 6
line 15: 8
line 9: 11
line 10: 13
line 12: 14
line 13: 17
line 15: 19
line 13: 22
line 14: 24
line 15: 25
line 16: 28
LocalVariableTable:
Start Length Slot Name Signature
27 0 t Ljava/lang/String;
10 1 e Ljava/lang/Exception;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
まずLocalVariableTable情報を見て、この中で2つの変数を定義しました.1つはt Stringタイプで、1つはe Exceptionタイプです.
次はコード部分を見て
[0-2]行目は、0番目の変数に「」、すなわちString t=「」;
[3-6]行目、すなわちtry文ブロック付与文、すなわちt=「try」を実行する.
7行目、ポイントは7行目で、s番目に対応する値「try」を3番目の変数に払いますが、この中の3番目の変数は定義されていません.これはおかしいです.
行目[8-10]は、0番目の変数、すなわちt=「finally」
[11-12]行目、3番目の変数に対応する値を返す
バイトコードにより、try文のreturnブロックでは、returnが返す参照変数(tは参照タイプ)はtry文外で定義された参照変数tではなく、システムが参照tに対応する値、すなわちtryを指し、finally文で参照tが値finallyを指しても、returnの戻り参照はtではないので、tを参照する対応する値はtry文の戻り値とは無関係です.
次に例を示す:(例2)
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
// result = "catch";
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
ここでは最初のコードを少し修正しましたが、finally文ブロックにreturn tの式を加えただけです.
最初のセグメントコードの解釈に従って、try{}文を先に行い、returnの前に現在のtの値tryを変数t'に保存し、finally文ブロックを実行し、変数tの値を変更し、変数tを返す.
この中には2つのreturn文がありますが、プログラムはtryかfinallyを返します.次にバイトコード情報を見てみましょう
public static final java.lang.String test();
Code:
Stack=1, Locals=2, Args_size=0
0: ldc #16; //String
2: astore_0
3: ldc #18; //String try
5: astore_0
6: goto 17
9: astore_1
10: ldc #20; //String catch
12: astore_0
13: goto 17
16: pop
17: ldc #22; //String finally
19: astore_0
20: aload_0
21: areturn
Exception table:
from to target type
9 9 Class java/lang/Exception
16 16 any
LineNumberTable:
line 5: 0
line 8: 3
line 9: 6
line 10: 9
line 12: 10
line 13: 13
line 14: 16
line 15: 17
line 16: 20
LocalVariableTable:
Start Length Slot Name Signature
19 0 t Ljava/lang/String;
6 1 e Ljava/lang/Exception;
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 0 /* same */
このコードが翻訳されたバイトコードは、最初のコードとはまったく異なり、code属性を見続けます.
[0-2]行、[3-5]行の第1セグメントのコード論理は類似しており、tを初期化し、try中のtをtryに付与する
6行目、ここで17行目にジャンプし、[17-19]はfinallyの付与文を実行し、変数tをfinallyに付与し、tに対応する値を返す
try文のreturn文が無視されることが分かった.jvmは、1つのメソッドに2つのreturn文があるのはあまり意味がないと考えているかもしれませんが、tryのreturn文は無視され、直接機能しているのはfinallyのreturn文なので、今回はfinallyを返します.
次に複雑な例を見てみましょう.(例3)
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
// System.out.println(t);
// return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
この中にtry文が投げ出されます JAva.lang.NumberFormatExceptionなので、プログラムはまずcatch文の中のロジックを実行し、tはcatchに割り当てられ、returnを実行する前に、戻り値を一時変数の中のt'に保存し、finallyのロジックを実行し、tはfinallyに割り当てられますが、戻り値とt'なので、変数tの値と戻り値はもう関係ありません.戻り値はcatchです
例4:
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
これは例2と少し似ていますが、try文に異常が投げ出されたため、プログラムはcatch文ブロックに移行し、catch文はreturn文を実行する前にfinallyを実行し、finally文にreturnがあればfinallyの文値を直接実行し、finallyを返します
例5:
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
Integer.parseInt(null);
return t;
} finally {
t = "finally";
//return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
この例ではcatch文ブロックにInteger.parser(null)文を追加し,強制的に例外を投げ出した.そしてfinally文ブロックにはreturn文はありません.引き続き分析すると、try文が異常を投げ出すため、プログラムはcatch文ブロックに入り、catch文ブロックはまた異常を投げ出し、catch文が終了することを説明するとfinally文ブロックを実行し、tに値を付与する.そしてcatch文ブロックに異常が投げ出されます.結果としてjava.lang.NumberFormatException異常を放出
例6:
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
Integer.parseInt(null);
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
この例と上記の例で唯一異なるのは、finally文にreturn文ブロックがあることです.try catchで実行される論理は、上記の例と同様に、catch文ブロックに異常が投げ込まれた後、finally文に入るのが速く、tを返します.プログラムはcatch文ブロックから投げ出された異常情報を無視し,tに対応する値であるfinallyを直接返す.方法は異常を投げ出さない
例7:
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (NullPointerException e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
この例では、catch文のcatchはjava.lang.NumberFormatException異常ではなくNPE異常なので、catch文ブロックには入らず、finally文ブロックに直接入り、finallyがsに値を付けた後、try文からjava.lang.NumberFormatException異常を投げ出す.
例8
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (NullPointerException e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
上記の例のtry catchの論理と同様に、try文はfinally文の実行を完了し、finallyはsを割り当て、sを返し、最後にプログラム結果はfinallyを返す
例9:
public class TryCatchFinally {
@SuppressWarnings("finally")
public static final String test() {
String t = "";
try {
t = "try";return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
String.valueOf(null);
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
この例ではfinally文にString.valueOf(null)を追加し,NPE異常を強制放出する.まずプログラムはtry文を実行し,戻り実行ではfinally文ブロックを実行し,finally文はNPE異常を放出し,結果全体はNPE異常を返す.
以上のすべての例をまとめる
1 try、catch、finally文では、try文にreturn文がある場合、返された後の現在のtryでの変数に対応する値が返され、その後変数を変更してもtryでのreturnの戻り値には影響しません.
2 finallyブロックにreturn文がある場合、tryまたはcatchの戻り文は無視されます.
3 finallyブロックから例外が投げ出された場合、try、catch、finallyブロック全体に例外が投げ出される
したがってtry,catch,finally文ブロックで注意すべきは
1 tryまたはcatchでreturn文をできるだけ使用します.finallyブロックでtryまたはcatchの戻り値を変更することはできません.
2 finallyブロックでreturn文の使用を避ける.finallyブロックでreturn文を使用すると、try、catchブロックの異常情報が消化され、エラーの発生が遮断されるからだ.
3 finallyブロックで再び例外を放出しないでください.そうしないと、try文ブロックを含むメソッド全体が例外を放出し、try、catchブロックの例外を消化します.