\r\n\r\n
プログラミングにおける例外とは、プログラム実行中のある時点における異常事態を指します。例外が発生した場所ではなく、他の場所で処理した方が良い場合に使用することができます。次のような例を考えてみましょう。
このような場合、例外が発生した場所以外で処理し、根本的な原因を解決する必要があります。
下図は、Javaの例外階層の主な部分を示しています。ベースクラスは破棄可能で、例外とエラーに分かれる。クラス例外は、状況を救済するためにアプリケーションがキャッチできるプログラム関連の条件に適用されます。一方、クラスエラーは、アプリケーションがキャッチしてはならないJava実行環境における深刻なエラーを示すために使用されます。例として、OutOfMemoryErrorやStackOverflowerErrorなどがあります。
チェックされた例外とチェックされていない例外の2種類があります。チェックされた例外は、呼び出し側のコードで処理する必要があります。このルールは、javaコンパイラによって強制されます。一方、チェックされていない例外は、明示的に宣言しなくても、コールチェーン上に伝播していくことができます。次の例で明らかにします。
次のメソッドは、ファイルから FileReader を作成しようとします。コンストラクタは選択された例外 FileNotFoundException をスローします。これは、呼び出し側のコードで処理するか、スローとして宣言する必要があります。
以下のコードは、コンパイルしてもしなくてもどちらでもないため、コンパイルできません。
private void loadFile(String filename){ FileReader in = new FileReader(filename);}コンパイルするコードを得るための一つの方法として、例外処理(後述)がある。
private void loadFile(String filename){ try { FileReader in = new FileReader(filename)); { } catch(FileNotFoundException ex) { // handle exception here }}呼び出し側が例外を直接処理できない場合は、メソッドのシグネチャで例外を宣言する必要があります。
private void loadFile(String filename) throws java.io.FileNotFoundException{ FileReader in = new FileReader(filename)); {}未チェック例外とは、RuntimeExceptionをサブクラス化した例外で、上記のように直接処理したり宣言したりする必要はない。例えば、以下のようなコードは、RuntimeExceptionの一種であるNullPointerExceptionとなる。しかし、NullPointerExceptionはチェックされていない例外であるため、コードはエラーなくコンパイルされます。
private void handleEvent(){ String name = null; if ( name.length() > 0 ) { }}上記のチェック付き例外とチェックなし例外の議論を踏まえると、チェックなし例外の方が自分で宣言したり処理したりする必要がないので、簡単に扱えそうです。このような利便性を考慮すると、チェックされた例外をチェックされていない例外で包むことが有効な場合もある。
次のコード例は、例外をラップする方法を示しています。メソッド \u 1() は、その本体で SQLException をスローします。コードを正しくコンパイルするために、スローする例外を宣言する必要があります。
private void method_1() throws SQLException { ... throw new SQLException;}このメソッドが他のメソッド (method_2()) から呼び出された場合、そのメソッドは SQLException をキャッチしてチェックされていない例外でラップできるため、そのメソッドのシグネチャで例外を宣言する必要はない。
private void method_2() { try { method_1(); } catch(java.sql.SQLException ex) { throw new RuntimeException(ex); }}例外スタックトレースは、アクティブなスタックフレームの配列を参照し、それぞれが例外がスローされたときにJVMによってキャッチされたメソッドコールを表しています。各スタックフレームには、クラス名、メソッド名、場合によってはJavaソースファイル名とファイル内の行番号など、メソッド呼び出しの位置が含まれています。エラーに至った一連の呼び出しを追跡するのに役立ちます。
以下は、例外オブジェクトのキャッチから取得した典型的なスタックトレースです。
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 8, Size: 5 at java.util.ArrayList.rangeCheck(ArrayList.java:653) at java.util.ArrayList.get(ArrayList.java:429) at sample.sample1.main(sample1.java:24)ここで捕捉される例外はIndexOutOfBoundsExceptionで、エラーに関する追加情報が含まれています。スタックトレースには、図のように3つのスタックフレームがあり、それぞれ位置情報を含んでいます。
例外は try-catch ブロックでキャッチし、必要な修正を行うことで対処できます。 Exception オブジェクトは、例外の原因となった状態に関する情報を抽出するためのいくつかの方法を提供します。
次のコードは、エラーメッセージをログファイルに記録します。
private void loadConfig() { try { // invoke code which might generate an IOException } catch(java.io.IOException ex) { // handle exception here. May be log to a log file. log.warning(ex.getMessage()); }}例外を別の例外で包む場合、包んだ例外を取り出すことができる。
Throwable cause = ex.getCause();log.warning("Underlying cause: " + cause.getMessage());スタックトレースへのアクセス、またはスタックトレースの原因となるメソッド名の抽出が必要ですか?
StringBuilder **uf = new StringBuilder("Stack Trace: ");for (StackTraceElement el : ex.getStackTrace()) { **uf.append(el.getClassName() + "." + el.getMethodName()).append("");}log.warning(**uf.toString());それとも、例外を記録して再試行しますか?
try { ...} catch(java.io.IOException ex) { log.warning(ex.getMessage()); throw ex;}Exceptionクラスは、スタックトレースを独自のPrintStream(またはPrintWriter)に出力するprintStackTrace()メソッドを提供します。
try { ...} catch(java.io.IOException ex) { PrintStream out = ...; out.println(ex.getMessage()); ex.printStackTrace(out);}1つのtryブロックで複数種類の例外をキャッチし、例外の種類ごとに特定の処理を行うことができます。
try { // throws some excepti*** here} catch(java.io.IOException ex) { // IOException specific handling here} catch(java.sql.SQLException ex) { // SQLException specific handling here}複数の例外型をキャッチしつつ、同じ処理コードを使用するには、以下のように複数の型を持つキャッチブロックを宣言します。
try { // throws some excepti*** here} catch(java.io.IOException | java.sql.SQLException ex) { // IOException and SQLException specific handling here} catch(SAXException ex) { // SAXException specific handling here}例外を発生させる可能性のあるコードを扱う場合は、あらゆるリソース(開いているファイル、データベース接続など)に対して適切なクリーンアップを行う必要があります。リソースのクリーンアップは、finally ブロックで実行される必要があります。こうすることで、ブロックの正常終了と異常終了の両方がクリーンアップコードを呼び出すことになります。
InputStream in = null;try { ... in = new FileInputStream(filename); ...} catch(java.io.IOException ex) { log.warning(ex.getMessage());} finally { // code here is executed on exiting the try block, // whether normally or due to an exception if ( in != null ) in.close();}Java 1.7では,try-with-resources構文が導入され,リソースのクリーンアップが容易になりました.
try( InputStream in = new FileInputStream(..) ) { // code which uses the InputStream.}InputStream変数は、コードがブロックを終了するときに(きれいに、または例外によって)自動的にクリアされます。
ブロックヘッダですべてのリソースを宣言することで、複数のリソースをクリーンアップする。
try( InputStream in = new FileInputStream(..); Connection con = ...; ) { // code which uses the InputStream and the Connection.}AutoCloseableインタフェースを実装しているクラスであれば、どのようなオブジェクトでもこの方法でクリアすることができます。以下のクラスは、close()メソッド内で特定のクリーンアップを行っています。
public class MyClass implements AutoCloseable { public void close() { // cleanup code here }}このようなインスタンスは、try with resources ブロックで使用します。
try( MyClass obj = new MyClass(..) ) { // code which uses the MyClass object.}では、よくある異常について見てみましょう。
例外は、Javaにおけるエラーの報告と管理の主要な方法であり、その適切な使用は、コードの品質を向上させ、実運用での問題解決を支援します。
戦争体験に関連する例外があれば、以下のコメント欄で教えてください。
画像引用元:Dmitry Nikolaev via Shutterstock.com