Javaの例外テクニックを知る:EclipseでJavaに強くなる(4)(3/3 ページ)
前回「EclipseでJavaの例外を理解する」では、Javaにおける例外の用途と基本的なコードの書き方、例外が発生するさまざまなケースについて理解しました。今回は、独自に例外を定義する方法や、ちょっとした例外のテクニックを紹介しましょう。
知っておくと得する例外のTips
finallyでリソースの解放を確実に
All-In-One Eclipseを使ってSampleFinallyクラスを新規に作成し、次のようなコードを入力してください。このプログラムはネットワークプログラミングでよく使われるソケットを使ったプログラムのひな型になります。Javaでソケットを表現するクラスとして用意されているjava.netパッケージのServerSocketクラスとSocketクラスを使っていますが、処理の内容はここでは考えなくても構いません。「new ServerSocket();」「ss.accept();」「s.close();」「ss.close();」のそれぞれの行で例外java.io.IOExceptionが投げられる点に注目をしてください。
import java.net.ServerSocket; import java.net.Socket; public class SampleFinally { public void execute() { ServerSocket ss = new ServerSocket(); Socket s = ss.accept(); System.out.println("accept"); // s を使った処理(省略) s.close(); ss.close(); } }
例外java.io.IOExceptionが投げられるのにキャッチをしていませんから、コンパイルエラーが発生していることを示す赤い下線が表示されます。Sample310のときと同じようにして対処しましょう。ただし、ここでは「スロー宣言の追加」を選びます。
すると、import java.io.IOException; がファイルの先頭に追加され、executeメソッドにthrows IOExceptionという宣言が追加されます(画面5)。このように、例外をキャッチせずに、そのままスローしてしまう対処方法もあります。この場合はここまでの例であったような「投げる例外クラスのインスタンスを生成する」といった処理は必要ありません。
さて、これでエラーはなくなりましたが、「new ServerSocket();」や「ss.accept();」「s.close();」でIOExceptionが発生すると、その時点で例外が投げられてしまうため、その先の処理は実行されません。これでは「ss.close();」が呼び出されないことになってしまいます。これではServerSocketを使用中の状態にしたままメソッドが終了したことになってしまいます。その結果、ServerSocketはOSに管理されているリソースを使っているので、これが解放されないことにつながります。
こういったことを防ぐためには、次のようにfinally節を使って確実にリソースが解放されるようにSocketクラスのcloseメソッドやServerSocketクラスのcloseメソッドを呼び出すようにする必要があります。
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class SampleFinally { public void execute() throws IOException? { ServerSocket ss = null; try { ss = new ServerSocket(); Socket s = null; try { s = ss.accept(); System.out.println("accept"); // s を使った処理(省略) } finally { if (s != null) s.close(); } } finally { if (ss != null) ss.close(); } } }
以上のようにfinally節を使うとリソースの解放を確実に行うことができます。ただし、finally節があると、finallyブロック内の処理が必ず実行されますから、返却値があるメソッドでは若干注意が必要になります。例えば次のようなコードはどういった結果になるでしょうか。tryブロックで「return name;」としていますが、finallyブロックで「return "Finally";」としてあるので"Finally"が返りそうです。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Sample340 { public String execute() { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Input name: "); String name; try { name = in.readLine(); return name; } catch (IOException e) { return "Exception"; } finally { return "Finally"; } } public static void main(String[] args) { Sample340 sample = new Sample340(); System.out.println(sample.execute()); } }
実際にサンプルを作って動作をさせてみましょう(画面6)。Eclipseでは「finallyブロックは正常に終了しません」といった警告が出ますがコンパイル自体は成功しています。この警告を無視して実行すると「return name;」はなかったことになり、「return "Finally";」が実行された結果が出力されます。混乱しないようにするためには「finallyブロックの中ではreturnしない」ということを基本にするのが良いでしょう。
Eclipseを使っていれば警告が表示されるので、あえて無視をしない限りこのようなコーディングをすることはないはずですが、この意味をしっかりと理解しておきましょう。
「コンパイル時にチェックされる例外」を使いたくないとき
「コンパイル時にチェックされる例外」を使用するには下記の注意点も考慮する必要があります。
- あるメソッドでjava.sql.SQLExceptionを投げるように設計すると、データベースを使っていることが分かってしまいます。つまり、実装の詳細が分かってしまうことがあります。
- メソッドが投げる例外を変更すると、例外をキャッチしているコードもすべて影響を受けてしまいます。開発中の場合は特に投げる例外を細分化したり統合したりと変更をしたくなることが多いですが、「コンパイル時にチェックされる例外」の変更は影響範囲が大きいため気軽に例外の設計を変更することができません。
- メソッドが投げる例外が多過ぎて、本来2、3行程度の単純なコードが10以上のcatch節を持つコードが必要になる場合があります。これによりコードの可読性が著しく低下することがあります。
このように、「コンパイル時にチェックされる例外」であるjava.lang.Exceptionクラスを継承する独自の例外クラスでは強制力があることにより使いにくい面もあります。従って、場合によってはjava.lang.RuntimeExceptionクラスを継承した独自の例外クラス設計を検討する価値はあります。ただし、このクラスを使用する場合には、どのメソッドがどんな例外を投げるのかについての情報を完全にドキュメント化しておく必要があります。java.lang.Exceptionクラスのサブクラスであればコンパイラがチェックをしてくれるのですが、java.lang.RuntimeExceptionクラスのサブクラスについては、そういったチェックがされないので、ドキュメントしか頼りになるものがなくなってしまうからです。
まとめ
以上のように、例外を使うとエラー処理をうまくコーディングできることが多いですから、よく理解して使えるようになってください。また、本文では説明をしませんでしたが、メソッドであればエラーコードなどを返すようにすることもできますが、コンストラクタではそういった処理をコーディングすることはできません。インスタンス生成時のエラー発生を捕捉するには例外は必須になります。
コンストラクタでのエラー捕捉には例外が必須となりますが、そうでない場合は、例外はあくまでエラーへ対応するための1つの手段でしかありません。アプリケーション全体を考えた場合は例外を使うよりも、メソッドでエラーコードを返すようにしてエラー処理をした方が良い場合もあります。どういった方法が良いのかを常に考えながらコーディングするようにしましょう。
また、例外を使うと処理の流れを変えることができますから、条件文のような使い方も可能です。しかし、例外はその名のとおり、「例外的な処理が発生したことを通知する、その通知を捕捉する」という目的で設計されています。処理の流れを変えるために例外を使うというのには何のメリットもありませんから、目的以外で使用することがないようにしましょう。
こういった点に注意しながら、例外処理の仕組みをよく理解したうえで有効に利用できるようになってください。次回は、型について解説をする予定です。お楽しみに。
筆者紹介
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。Ja-Jakartaプロジェクト(http://www.jajakarta.org/)へ参加し、コミッタの一員として活動を支えている。また、長野県の地域コミュニティである、SSS(G)(http://www.sssg.org/)やbugs(J)(http://www.bugs.jp/)の活動へも参加している。
Copyright © ITmedia, Inc. All Rights Reserved.