JavaやらPHPとかの例外処理をやっていたら、Delphiの例外処理って特殊というか、誤って理解している部分があったと認識しましたので、改めてまとめてみました。
Javaの例外処理
Javaの例外処理はスッキリしている(と思います)。
try {
例外が起きるかもしれない処理
} catch(AException e) {
例外AExceptionが発生した場合の処理
} catch(BException e) {
例外BExceptionが発生した場合の処理
} catch(CException | DException e) {
例外CExceptionかDExceptionが発生した場合の処理
} finally {
例外が起きても起きなくても行う処理
}
tryブロックを抜けたら行う処理
catch節は、捕捉する例外に応じて続けることができます。finally節は、処理が不要なら省略できます。
なお、Java 7以降ではtry~catch~resources文が使えるようになっているそう。これはfinally節で明示的に行っていたリソースの解放を明示的な記述なしに行える書式ですが、この投稿の趣旨とは関係ないので省略します。
PHPの例外処理
PHPの例外処理は、Javaに倣って作ったのか、構文も動きもほとんど同じです(多分)。catch節で、捕捉する例外をパイプ(|)で結んで複数指定できるのもJavaと同じです。
Delphiの例外処理
これに対して、Delphiの例外処理はちょっとややこしいです。まず、書式が2つあること。
try
例外が起きるかもしれない処理
except
on E:AException do 例外AExceptionが発生した場合の処理
on E:BException do 例外BExceptionが発生した場合の処理
end;
tryブロックを抜けたら行う処理
try
例外が起きるかもしれない処理
finally
例外が起きても起きなくても行う処理
end;
tryブロックを「何事もなく」抜けたら行う処理
双方が合わさったtry~except~finallyというものは存在しないのです。
しかも、try~finallyでは例外が起きてもっとも外側のfinallyブロックの中身を実行したら、プロシージャなり関数なりからも抜けてしまう、つまりexitしてしまうので要注意です。つまり、finallyブロック以降に書かれた処理は実行されません。本当に、「最後に」実行する処理になります。
そういう意味では、try~finallyは厳密には例外処理ではないかもしれません。処理されない例外を捕捉して、後処理までやってあげたよ。けど処理を続けても意味がないので抜けるね、という感じでしょうか。
んじゃ、Javaなんかのようにするにはどうしたらいいの?ということですが、これはtryブロックを入れ子にするしかないようです。
try
try
例外が起きるかもしれない処理
except
on E:AException do 例外AExceptionが発生した場合の処理
on E:BException do 例外BExceptionが発生した場合の処理
end;
finally
例外が起きても起きなくても行う処理
end;
tryブロックを抜けたら行う処理
内側のtryブロックで例外が発生したら、そこのexceptブロックにある例外処理が実行されます。そして、例外の有無にかかわらず外側のfinallyブロックに処理が移ります。ここで大事なのは、外側のtryブロックを抜けても処理が継続するということです。内側のtryブロックで例外が発生しても、それはexceptブロックで処理されて例外がクリアされているので、finallyブロックにとっては例外が発生していないよ、ということになります。ややこしいですね。
とはいえ、これでJavaのような例外処理が実装できるわけです。
ちなみに、tryブロック内でexitしても、すべてのfinallyブロックが実行されますが、もっとも外側のfinallyブロックより後の処理は実行されません。つまり、exitするけど、すべてのfinallyをやっておくよ、ということになります。
ということで、何重にも入れ子になったtry~endは可読性を下げそうなので、できるだけ避けたほうが無難ですね。