堅牢なアプリケーションを目指す際に、エラーや状態のチェック、適切なクリーンアップ、リカバリは重要なポイントになります。このTipsでは、クリーンアップ処理について覚えておきたいテクニックを紹介します。
Javaプログラミングではメモリ管理を意識するケースは非常に少ないといえますが,ファイルやネットワーク,データベースなどシステムリソースの多くは,適切なクリーンアップをしなければなりません。ついメモリと同じ感覚でほったらかしにしておくと,すぐにメモリリークと同じ状態に陥ります。
たとえば,ファイルを開いたあとで何らかの処理を行い,不都合があった場合はファイルを閉じてメソッドから戻ります。次のようなコードになります。
FileInputStream fis = new FileInputStream……
if (チェック){
// 不都合があった場合
fis.close();
return ;
}
// 正常時の処理
:
fis.close();
ここで,チェックする内容が複数ある場合,if文を複数列挙することになるでしょう。
FileInputStream fis = new FileInputStream……
if (チェック){
// 不都合があった場合
fis.close();
return ;
}
if (チェック){
// 不都合があった場合
fis.close();
return ;
}
if (チェック){
// 不都合があった場合
fis.close();
return ;
}
// 正常時の処理
:
fis.close();
チェックの論理式を反転させて記述するスタイルもありますが,多かれ少なかれこのようなスタイルになります。そして,このようなスタイルに記述上の冗長性を感じている読者も少なくないと思います。しかし,ここに潜むのは冗長性の問題だけでなく,チェック項目が増えて追加したときに,追加したif文にだけcloseメソッド呼び出しを忘れたりします。つまり,ポイントはクリーンアップ処理が複数に分散することです。
このようなとき,Javaではtry-catchを使うとスマートに記述できます。正確には,catchを利用せずに,try-finallyを利用します。具体的には,次のように記述します。
FileInputStream fis = new FileInputStream…… // 例外は外に投げる
try
{
if (チェック){
// 不都合があった場合
return ;
}
if (チェック){
// 不都合があった場合
return ;
}
if (チェック){
// 不都合があった場合
return ;
}
// 正常時の処理
:
}
finally
{
fis.close();
}
一般的にtryとくればcatchを思い浮かべますが,catchを抜かしてfinallyを書くことができます。そのため,クリーンアップ処理が必要なケースでは,finallyを有効活用するとよいでしょう。これが今回のTipsのポイントです。
なお,このケースでは,new FileInputStreamに対する例外をどうするのかが気になると思いますが,それは別のtry-catchで処理します。クリーンアップが不要な段階の例外は,このブロック(メソッド)から外に投げてしまいます。それに関連してもう少し考えてみましょう。
たとえばファイルコピーを行う場合のように,クリーンアップの必要なリソースを2つ以上確保して,それらの解放について考えなくてはならないことがあります。このような場合にもfinallyによるクリーンアップが有効です。
ただし,処理が複雑になれば複雑になるほど,コードはシンプルにすべきです。複数のシステムリソースを1つのメソッドの中で処理すると,ともすれば片一方だけ解放を忘れたり,まだ確保していないリソースを解放してしまったりします。
複数のクリーンアップが必要なケースは,潔く別のメソッドにしてしまうといいでしょう。次のようなコードになります。
/**
コピー元のファイルを開きます。
*/
public void doFileCopy()
throws java.io.IOException
{
FileInputStream src = new FileInputStream……
try
{
if (チェック){
// 不都合があった場合
return ;
}
// 正常時の処理
doCopy(src);
}
finally
{
src.close();
}
}
/**
コピー先のファイルを開き,コピーします。
*/
private void doCopy(FileInputStream src)
throws java.io.IOException
{
FileOutputStream dest = new FileOutputStream……
try
{
if (チェック){
// 不都合があった場合
return ;
}
// 正常時の処理
// ここではsrc→destのコピー
}
finally
{
dest.close();
}
}
この例で見ると,ファイルコピーを行うdoFileCopyメソッドがIOExceptionを投げることは何も不自然ではありません。その代わりに,メソッド内の不測の事態に関しては責任を持つということです。
ここでは簡単な例で紹介しましたが,ポイントはcatchがなくてもfinallyを使えるという点です。非常に単純ですが,意外と忘れられがちなテクニックです。ぜひ覚えておいてください。
「JAVA Developer」より毎週役立つJava Tipsを配信中。ほかにも参考になるTipsは、JAVA Developerサイトのバックナンバーから探すことが可能です。
Copyright(C) 2010 SOFTBANK Creative Inc. All Right Reserved.