11章 例外 | |
1.例外とは | |
例外とは予期しなかった処理のことをいう。例えば、配列の中身の平均値を求めるメソッドを作ろうとした場合、かきのようになる。 | |
class class Test{ public static void main(String args[ ] ){ int array[ ] = new int[5]; array[0] = 10; public static int average(int[ ] array){ return total / array.length; |
|
C:\java>javac Test.java C:\java>java Test average=6 C:\java> |
|
ところが、配列の長さが0だった場合はaverageメソッドの中で「0で割る」という計算が発生してしまいエラーになってしまう。 | |
class class Test{ public static void main(String args[ ] ){ // int array[ ] = new int[5]; int array [ ] = new int [0]; /* array[0] = 10; array[1] = 5; array[2] = 2; array[3] = 4; array[4] = 9; /* System.out.println("average="+average(array)); } public static int average( int[ ] array ){ return total / array.length; |
|
C:\java>javac Test.java C:\java>java Test Exception in thread "main" java.lang.ArithmeticException: / by zero at Test.average(Test.java:20) at Test.main(Test.java:12) C:\java> |
|
このように予期せぬエラーが発生するとJavaではそのスレッドが終了してしまう。この例のように単一スレッドの場合はプログラムが終了してしまう。 このエラーメッセージにある「Exception」というのが例外をあらわしており、「in thread "main"」がスレッドをあらわしている。そしてその後の「java.langArithmeticException:/ by zero」が例外の内容をあらわしていて、ここではゼロで割ったという例外であることがわかる。次の行からどの部分で例が起ったかをあらわしている。Test.javaの20行目、Testクラスのaverageメソッドで起っていることがわかり、さらに次の行を見ると、Test.averageメソッドはTest.javaの12行目(Testクラスのmainメソッド)から呼び出されていることがわかる。 |
|
2.例外が起きたら | |
例外が起きた場合に、アプリケーションが終了してしまっては困る。そこでプログラマーは例外を予測して、例外が起きた場合に適当な処理をさせる必要がある。 例えば平均を求めるメソッドの場合、引数で与えられた配列がnewし忘れたなどで、配列の実体がなかった場合などは、 NullPointerExceptionという名前の例外が発生する。平均を求めるメソッドでは、この2つの例外が発生する可能性があるので、例外が起きた場合専用の処理をするようにする。 |
|
public class Test{ public static void main(String args[ ] ){ // int array[ ] = new int[5]; int array [ ] = new int [0]; System.out.println("average="+average(array)); public static int average( int[ ] array ){ return total / array.length; } } |
|
C:\java>javac Test.java C:\java>java Test 配列が空です average=0 C:\java> |
|
上記のように、例外が発生すると思われる部分を | |
try{ // 例外が発生する可能性のあるブロック }catch(例外の種類1 param1){ // 例外の種類1が発生した場合に実行される部分 }catch(例外の種類2 param1){ // 例外の種類2が発生した場合に実行される部分 }finally{ // 例外が起きても起きなくても実行される部分 } |
|
上記の構文を用いた場合、Jvmはtryブロックを実行する。もしtryブロックの中で例外が発生しなければ、catchブロックは実行せずに、finallyブロックを実行する。 もし、tryブロック実行中に例外が発生したら、発生した場所から例外の内容に応じてcatchブロックが実行される。この場合、tryブロックはすべてが実行されるわけではなく、例外が発生した部分よりも下の部分は実行されずにcatchブロックにジャンプする。上記の例で、averageメソッドの引数にnullが渡された場合、for分のarray.lengthでNullPointerExceptionという例外が発生する。したがって、forの中身は実行されずにNullPointerExceptionをキャッチしているブロックに処理が移行する。 なお、finallyブロックは省略することができる。catchブロックは1つ以上必要になる。 |
|
3.例外の種類 | |
ClassNotFoundException | 呼び出そうとしたクラスのクラスファイルが無かった場合 |
CloneNotSupportedException | オブジェクトを複製するためにObjectクラスのcloneメソッドが呼び出されたが、そのオブジェクトのクラスがCloneableインターフェイスを実装していないことを示すためにスローされる。 |
DataFormatException | データ形式エラーが発生したことを通知する |
IOException | なんらかの入出力例外の発生を通知するシグナルを発生させる。 サブクラスとしてEOFException,FileNotFoundExceptionなどがある。 |
PrinterException | 印刷システムに例外的な状態が発生したことを示すために使用される。 サブクラスとしてPrinterAbortException,PrinterIOExceptionがある。 |
RuntimeException | JVMの通常の処理でスローすることができる各種の例外のスーパークラス。 |
ArithmeticException | RuntimeExceptionのサブクラス。 算術計算で例外的条件が発生した場合にスローされる。例えば、「ゼロで除算される」整数はこのクラスのインスタンスをスローする。 |
ArrayStoreException | RuntimeExceptionのサブクラス。 不正な型のオブジェクトをオブジェクトの配列に格納しようとしたことを示すためにスローされる。 |
ClassCastException | RuntimeExceptionのサブクラス。 あるオブジェクトを継承関係にないクラスにキャストしようとしたことを示すためにスローされる。 |
IllegalArgumentException | RuntimeExceptionのサブクラス。 不正な引数、または不適切な引数をメソッドに渡したことを示すためにスローされる。 |
IndexOutOfBoundsException | RuntimeExceptionのサブクラス。 ある種のインデックス(配列、文字列、ベクトルなど)が範囲外であることを示すためにスローされる。サブクラスにArrayIndexOutOfBoundsException,StringIndexOutOfBoundsExceptionがある。 |
NegativeArraySizeException | RuntimeExceptionのサブクラス。 負のサイズを持った配列をアプリケーションが作成しようとした場合にスローされる。 |
NoSuchElementException | RuntimeExceptionのサブクラス。 この列挙にそれ以上の要素がないと、EnumerationのnextElementメソッドによってスローされる。 |
NullPointerException | RuntimeExceptionのサブクラス。 オブジェクトが必要な場合に、アプリケーションがnullを使おうとするとスローされる。 |
SecurityException | RuntimeExceptionのサブクラス。 セキュリティマネージャによってスローされ、セキュリティ違反を示す。 サブクラスにAccessControlException,RMISecurityExceptionがある。 |
NumberFormatException | IllegalArgumentExceptionのサブクラス。 アプリケーションが文字列を数値型に変換しようとしたとき、文字列の形式が正しくない場合にスローされる。 |
4.catchブロックの検索 | |
tryブロック内で例外が発生した場合は、tryブロックの次に記述されているcatchブロックに対応する例外処理が書かれているかを検索する。しかし、対応する例外処理が書かれていなければ(対応する例外をキャッチするcatchブロックがない場合)、そのtryブロックを囲っているさらに上のtryブロックにかかれているcatchブロックを検索する。つまり、例外処理が起きたメソッドを呼び出している呼び出し元のtry〜catchブロックなどを探す。そこにも発生した例外をキャッチするcatchブロックが無ければ、さらに呼び出し元を検索する。 可能な限りさかのぼってもキャッチされない場合は、そのスレッドはJVMによって強制的に終了されてしまう。もし単一スレッドであった場合は、アプリケーションが終了する。 |
|
public class Test{ public static void main(String args[ ] ){ try{ a( ); }catch (NullPointerException e){ System.err.println(e); } System.out.println("アプリケーションは正常に終了しました"); } public static void a( ){ public static void b( ){ int array[ ] = new int[2]; // ArrayIndexOutOfBoundsExceptionを発生 array[3] = 10; } } |
|
C:\java>javac Test.java
C:\java>java Test |
|
例えば、NullPointerExceptionはRuntimeExceptionのサブクラスで、RuntimeExceptionはExceptionのサブクラスなので下記のように記述する。 | |
public class Test{ public static void main(String args[ ] ){ try{ int.array[ ] = null; array[0] = 10; }catch(NullPointerException e{ System.err.println("NullPointerException"); }catch(RuntimePointerException e{ System.err.println("RuntimeException"); }catch(Exception e){ System.err.println("Exception"); } } |
|
C:\java>javac Test.java
C:\java>java Test |
|
ところが、下記の例では、スーパークラスのExceptionから記述しようとしたため、すべての例外は最初のExceptionクラスのcatchブロックでキャッチされてしまう。キャッチブロックは一回の例外で一度しか処理しないため、その下のRuntimeExceptionやExceptionやNullPointerExceptionではキャッチされない。したがって、RuntimeExceptionやNullPointerExceptionのキャッチブロックは処理されることがないため、コンパイルエラーになる。 | |
public class Test{ public static void main(String args[ ] ){ try{ int.array[ ] = null; array[0] = 10; }catch(Exception e{ System.err.println("Exception"); }catch(RuntimeException e{ System.err.println("RuntimeException"); }catch(NullPointerException e){ System.err.println("NullPointerException"); } } } |
|
C:\java>javac Test.java Test.java:8 catch not reached. }catch(RuntimeException e){ ^ Test.java:10: catch not reached. }catch(NullPointerException e){ ^ 2 errors C:\java> |
|
5.throw | |
例外は受け取るだけでなく、呼び出し元に例外処理を任せることもできる。これを「例外を投げる」という。例えば、そのキャッチブロックで例外処理をしただけではダメで、呼び出しもとのキャッチブロックでも例外処理をしなければならないような場合に使う。 下記の例ではaメソッド内で例外処理をした後、throwステートメントを用いて、例外を上に投げている。 |
|
public class Test{ public static void main(String args[ ] ){ try{ a( ); }catch(RuntimeException e){ System.err.println("mainメソッド内で例外を処理しました("+e+")"); } System.out.println("アプリケーションは正常に終了しました"); } public static void a( ) try{ int array[ ] = new int[2]; // ArrayIndexOutOfBoundsExceptionを発生 array[3] = 10; }catch(RuntimeException e){ System.err.println("aメソッド内で例外を処理しました("+e+")"); throw e; } } } |
|
C:\java>javac Test.java C:\java>java Test aメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException) aメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException) アプリケーションは正常に終了しました C:\java> |
|
上記の例では、生成されたRuntimeExceptionのインスタンスをthrowで上に投げている。throwステートメントは生成されたExceptionを投げるだけでなく、プログラマーが明示的にnewした例外などを上に投げることもできる。投げることができるのは、Throwableクラスのサブクラスだけである。Stringクラスなどのインスタンスは投げることができない。ThrowableクラスのサブクラスはExceptionクラス(すべての例外のスーパークラス)と、Errorクラスである。 | |
public class Test{ public static void main(String args[ ] ){ int array[ ] = new int[2]; try{ a(array, 10); }catch(RuntimeException e){ System.err.println("mainメソッド内で例外を処理しました("+e+")"); } } public static int a(int array[ ], int i ){ System.out.println("aメソッドは正常に終了します"); |
|
C:\java>javac Test.java C:\java>java Test ArrayIndexOutOfBoundsExceptionを投げます mainメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException) C:\java> |
|
6.throws | |
例外を投げる可能性があるメソッドを作成した場合は、他のプログラマーが見てもすぐに例外が投げられる可能性があることがすぐわかるように、明示しておくべきである。明示するためには、例外を投げる可能性があるコンストラクタやメソッドの宣言時にthrowステートメントを用いる。 | |
public class Test{ public static void main(String args[ ] ){ int array[ ] = new int[2]; try{ a(array, 10); }catch(RuntimeException e){ System.err.println("mainメソッド内で例外を処理しました("+e+")"); } } public static int a(int array[ ], int i ) throws ArrayIndexOutOfBoundsException{ System.out.println("aメソッドは正常に終了します"); |
|
C:\java>javac Test.java
C:\java>java Test C:\java> |
|
投げる例外が複数ある場合は、カンマ区切で列挙する。 | |
throwステートメントは記述しなくても問題ないが、特に複数人でプログラミングする場合など、自分以外の人にわかりやすくするために記述することが好ましい。また、1人でプログラムしていても、しばらくたつと忘れてしまう場合もあるため、記述することを勧める。 | |
7.エラーと例外 | |
throwできるクラスはThrowableクラスのサブクラスだけである。ThrowableクラスにはErrorクラスとExceptionクラスの2つがある。一般にアプリケーションの使い方をユーザーが間違えた場合、ファイルをセーブしようとしたのにディスクがいっぱい・通信の失敗など一般に予期することができるようなエラーはJavaではエラーではなく例外として処理する。逆にJVMの重大なエラーを示す。例えばメモリー不足、スタックオーバー、内部エラー、不正アクセスなど、プログラマーやユーザーのせいではないエラークラスとする。したがって、一般にはエラークラスのサブクラスを作成することはできるが一般にはしない。 Throwableクラス、Errorクラス、Exceptionクラスのコンストラクタとメソッドを紹介する。これらのサブクラスを作る方法は、必要なメソッドをオーバーライドする必要がある。 |
|
Throwableクラスのメソッドの概要 Throwable( ) エラーメッセージ文字列としてnullを指定してThrowableを新しく構築する Throwable(String message ) 指定されたエラーメッセージを持つ、Throwableを新しく構築する |
|
Throwableクラスのメソッドの概要 | |
Throwable | fillInStackTrace( ) 実行スタックトレースを埋め込む。 |
String | getLocalizedMessage( ) Throwableの地域対応された記述を作成する。 |
String | getMessage( ) このThrowableの地域対応された記述を作成する。 |
Void | printStackTrace( ) Throwableとそのバックトレースを標準エラーストリームに出力する。 |
Void | printStackTrace(PrintStream s) Throwableとそのバックトレースを指定された印刷ストリームに出力する。 |
Void | getStackTrace(PrintWriter s) このThrowableとそのバックトレースを指定されたプリントライターに出力する。 |
String | toString( ) Throwableオブジェクトの短い記述を返す。 |
Errorクラスのコンストラクタの概要 Error( ) 詳細メッセージを指定しないでErrorを構築する。 Error(String s) 指定された詳細メッセージを持つErrorを構築する。 |
|
Errorクラスのコンストラクタの概要 Exception( ) 詳細メッセージを指定しないでExceptionを構築する。 Exception(String s) 指定された詳細メッセージを持つExceptionを構築する。 |
|
Throwable,Error,Exception
public Throwable( ) エラーメッセージ文字列としてnullを指定してThrowable,Error,Exceptionを新しく構築する。 |
|
Throwable,Error,Exception
public Throwable(String message) 指定されたエラーメッセージを持つ、Throwable,Error,Exceptionを新しく構築する。 |
|
getMessage
public string getMessage( ) このThrowableオブジェクトのエラーメッセージ文字列を返す。 |
|
toString
public String toString( ) Throwable オブジェクトの短い記述を返す。このThrowable,Error,Exceptionオブジェクトがエラーメッセージ文字列付きで作成された場合、結果は次の3つの文字列を連結したものとなる。 |
|
printStackTrace public void printStackTrace( ) Throwable,Error,Exceptionとそのバックトレースを標準エラーストリームに出力する。 |
|
8.独自の例外 | |
Exceptionクラスを継承して独自の例外クラスを作成することもできる。これはアプリケーション特有の例外を作成したい場合などに使用される。 独自のエラークラスを投げるメソッドでは、必ずキャッチするか、throwsステートメントで、独自の例外を投げる可能性があることを明示しなければならない。 |
|
// 独自の例外クラス class MyException extends Exception{ public MyException( ){ super( ); } } public class Test{ public static void a( ) throws MyException{ |
|
C:\java>javac Test.java
C:\java>java Test |
|
上記の例で、throw MyExceptionやmainメソッド内のcatchを忘れると、コンパイルエラーになる。 ただし、独自の例外がRuntimeExceptionのサブクラスの場合は、この制限は適用されない。 |
|
// 独自の例外クラス class MyException extends Exception{ public MyException( ){ super( ); } } public class Test{ public static void a( ){ |
// 独自の例外クラス class MyException extends RuntimeException{ public MyException( ){ super( ); } } public class Test{ public static void main(String args[ ]){ try{ a( ); } catch(MyException e ){ System.err.println(e); } } public static void a( ){ |
C:\java>javac Test.java Test.java:11: Exception MyException is never thrown in the body of the corresponding try statment. }catch(MyException e){ Test.java:17: Exception MyException must be caught, or it must be declared in the throws clause of this method. throw new MyException( ); 2 errors C:\java> |
C:\java>javac Test.java
C:\java> |