これは私自身の質問に答える試みですが、実験と Java コンパイラから得られた結果を使用しているため、特に哲学などに取り組んでいるわけではありません。
以下は、catch-cleanup-and-rethrow のサンプル コードです。
public CompoundResource catchThrowable() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
return new CompoundResource(stream1, stream2);
} catch (Throwable t) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
throw t;
}
}
これは、次のバイトコードにコンパイルされます。
public Exceptions$CompoundResource catchThrowable() throws java.lang.Exception;
Code:
0: aconst_null
1: astore_1
2: aconst_null
3: astore_2
4: new #2 // class java/io/FileInputStream
7: dup
8: ldc #3 // String 1
10: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
13: astore_1
14: new #2 // class java/io/FileInputStream
17: dup
18: ldc #5 // String 2
20: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
23: astore_2
24: new #6 // class Exceptions$CompoundResource
27: dup
28: aload_0
29: aload_1
30: aload_2
31: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V
34: areturn
35: astore_3
36: aload_2
37: ifnull 44
40: aload_2
41: invokevirtual #9 // Method java/io/InputStream.close:()V
44: aload_1
45: ifnull 52
48: aload_1
49: invokevirtual #9 // Method java/io/InputStream.close:()V
52: aload_3
53: athrow
Exception table:
from to target type
4 34 35 Class java/lang/Throwable
次は、check-for-failure-in-finally-and-cleanup のコードで、それ以外は同じセマンティクスです。
public CompoundResource finallyHack() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
boolean success = false;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
success = true;
return new CompoundResource(stream1, stream2);
} finally {
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
}
}
これは次のようにコンパイルされます。
public Exceptions$CompoundResource finallyHack() throws java.lang.Exception;
Code:
0: aconst_null
1: astore_1
2: aconst_null
3: astore_2
4: iconst_0
5: istore_3
6: new #2 // class java/io/FileInputStream
9: dup
10: ldc #3 // String 1
12: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
15: astore_1
16: new #2 // class java/io/FileInputStream
19: dup
20: ldc #5 // String 2
22: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
25: astore_2
26: iconst_1
27: istore_3
28: new #6 // class Exceptions$CompoundResource
31: dup
32: aload_0
33: aload_1
34: aload_2
35: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V
38: astore 4
40: iload_3
41: ifne 60
44: aload_2
45: ifnull 52
48: aload_2
49: invokevirtual #9 // Method java/io/InputStream.close:()V
52: aload_1
53: ifnull 60
56: aload_1
57: invokevirtual #9 // Method java/io/InputStream.close:()V
60: aload 4
62: areturn
63: astore 5
65: iload_3
66: ifne 85
69: aload_2
70: ifnull 77
73: aload_2
74: invokevirtual #9 // Method java/io/InputStream.close:()V
77: aload_1
78: ifnull 85
81: aload_1
82: invokevirtual #9 // Method java/io/InputStream.close:()V
85: aload 5
87: athrow
Exception table:
from to target type
6 40 63 any
63 65 63 any
ここで何が起こっているかを注意深く見ると、return の時点と catch ブロック内の両方で、finally ブロック全体を複製した場合と同じバイトコードが生成されているようです。つまり、次のように書いたようなものです。
public CompoundResource finallyHack() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
boolean success = false;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
success = true;
CompoundResource result = new CompoundResource(stream1, stream2);
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
return result;
} catch (any t) { // just invented this syntax, this won't compile
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
throw t;
}
}
誰かが実際にそのコードを書いたら、あなたは彼らを笑うでしょう。成功ブランチでは、成功は常に真であるため、実行されないコードの大部分が存在するため、実行されないバイトコードが生成され、クラス ファイルが肥大化するだけです。例外分岐では、成功は常に false であるため、実行する必要があることがわかっているクリーンアップを実行する前に、値に対して不要なチェックを実行しているため、クラス ファイルのサイズが増加します。
注意すべき最も重要なことは次のとおりです。
catch (Throwable)
とソリューションの両方がfinally
実際にすべての例外をキャッチします。
Throwable
では「後片付けをするために捕まえてもいいですか?」という質問に答える限り……。
まだよくわかりませんが、キャッチThrowable
してもダメなら使ってもダメなのはわかってfinally
います。そして、finally
どちらもOKでなければ、何が残りますか?