例外処理は、エラー処理に似ている機能です。
例えば、Javaでエラー処理するとしたら、以下のような感じになります。
if (エラー発生) {
エラー処理
}
上記のようなif文等によるエラー処理記述は、
ソースファイルの至るところに記述されるため、
エラー処理ではない”プログラム本来の記述”と
区別するのが難しくなってくる場合があります。
こういった場合のためにJavaでは、
エラー処理の役目を、例外処理と言う機能として用意しています。
例外処理の基本的な使い方
上記で示したJavaによるエラー処理を、
例外処理を使って表現すると以下のような感じになります。
class MyException extends Exception { } // 例外用の空オブジェクト
public class Cmain {
public static void main(String[] args) {
try {
if (エラーor例外発生) {
throw new MyException();
}
}
catch (MyException e) {
System.out.println("MyException型の例外が" +
"発生しました。");
}
}
}
例外処理では、try(トライ)ブロックとcatch(キャッチ)ブロックを
1セットとして記述します。
tryブロックでは、tryブロックで囲われたプログラム中で
例外が発生しているかどうか
(エラー等が起こっているかどうか)チェックします。
つまり、例外(エラー等)が無いかtry(試す)と言う意味です。
例外を発生させる場合には、throw(スロー)キーワードを使います。
tryブロック内の処理で例外(エラー等)が発生した時には、
throwを使い、例外が起こった時の情報として、
例外用のオブジェクトを対応するcatchブロックに届ける事ができます。
この例の場合は、オブジェクト:MyExceptionをcatchに届けます。
例外用オブジェクトを自分で定義する
例外用のオブジェクトは自分で新たに定義しなければなりません。 この例外用のオブジェクトは、 Exceptionオブジェクトを継承したモノであれば、 オブジェクト名は何でもOKです。 ただ、定義の中身は空っぽでも十分機能する事が多いので、 通常は中身を空にします。 |
throwと言うキーワードは、例外情報をthrow(投げる)と言う意味です。
tryブロックで例外が発生すると、throw文以降の処理が実行されずに
対応するcatchブロックに処理が移ります。
そして、catchブロックでは、
throwされた例外オブジェクトが関数の引数のように渡され、
その情報によっていろんなエラー対応処理を記述する事が可能になります。
catchブロックが処理された後は、一つのtry・catchブロックを抜け、
順番通りにプログラムが進行します。
間接的に例外を発生させる
tryブロック内に直接throw文を書かずに、
tryブロック内の関数の中でthrow文を書く、と言った事も可能です。
class MyException extends Exception { }
public class Cmain {
public static void main(String[] args) {
try {
func(); // tryブロック内にthrowがないが、
} // func関数内にthrowがある。
catch (MyException e) {
System.out.println("MyException型の例外が発生");
}
}
static void func() {
~何かの処理~
if (エラーor例外発生) {
throw new MyException();
}
}
}
異なるオブジェクト型による複数の例外処理
throw文では、例外情報として、例外用オブジェクトを
対応するcatchブロックに届ける事ができます。
と説明しました。
それぞれの異なる型による例外情報は、
それぞれの型に対応するcatchブロックを記述して対応します。
例えば、例外発生時の情報がMyException型の数値なら
MyException型の引数を持つcatchブロックに情報が渡されます。
class MyException extends Exception { }
class MyException2 extends Exception { }
public class Cmain {
public static void main(String[] args) {
try {
if (エラーor例外1発生) throw new MyException();
if (エラーor例外2発生) throw new MyException2();
}
catch (MyException e) {
System.out.println("MyException型の例外が発生");
}
catch (MyException2 e) {
System.out.println("MyException2型の例外が発生");
}
}
}
この場合、MyExceptionによる例外オブジェクトと、
MyException2による例外オブジェクトが
throw文で投げられる可能性があるので、
catchブロックは、対応するために、
MyException型・MyException2型に対応する
2つのcatchブロックを記述しています。
MyExceptionが投げられたら、MyException対応のcatchブロックへ、
MyException2が投げられたら、MyException2対応のcatchブロックへ、
処理が移ります。
その他の型による例外処理
前述では、MyException型とMyException2型による
例外処理を示しましたが、
そのどちらでもない例外用オブジェクトが投げられた場合はどうなるでしょうか?
こんな時は、どんな例外用オブジェクトでも受け取れる
方法でcatchブロックを記述しなければなりません。
class MyException extends Exception { }
class MyException extends Exception2 { }
class MyException extends Exception3 { }
public class Cmain {
public static void main(String[] args) {
try {
if (エラーor例外1) throw new MyException();
if (エラーor例外2) throw new MyException2();
if (エラーor例外3) throw new MyException3();
}
catch (MyException e) {
System.out.println("MyException型の例外が発生");
}
catch (MyException2 e) {
System.out.println("MyException2型の例外が発生");
}
catch (Exception e) {
System.out.println("不明な例外が発生");
}
}
}
このプログラムでは、エラーor例外3の時に
例外用オブジェクトのMyException3が投げられています。
しかし、MyException3に対応するcatchブロックは記述されていないように見えます。
ですが、catchブロックの引数にExceptionと書く事により、
全ての例外用オブジェクトを受け取れます。
つまり、例外情報がMyException型のcatchブロックと
MyException2型のcatchブロックのどちらにも
当てはまらない例外オブジェクトが投げられた時は、
引数にExceptionと記述されているcatchブロックに処理が移ります。
このExceptionと記述するタイプのcatchブロックは
複数のcatchブロックの最後に書きます。
例外処理のネスト(多重構造化)
例外処理ブロックのtry~catchブロックは多重構造化する事が可能です。
つまり、tryブロックの中にtry~catchブロックがある状態です。
めんどくさそうですが、1つ1つ見ていけばそれほど複雑ではありません。
class MyException extends Exception {
public int int1;
public MyException(int data) {
int1 = data;
}
}
public class Cmain {
public static void main(String[] args) {
// try1
try {
if (例外1) throw new MyException(1);
// try2
try {
if (例外2) throw new MyException(2);
if (例外3) throw new MyException(3);
}
// catch2
catch (MyException e) {
System.out.println("MyException型の例外が" +
"発生");
if (e.int1 == 3) throw;
}
}
// catch1
catch (MyException e) {
System.out.println("MyException型の例外が発生");
}
}
}
このプログラム例の場合は、
まず、try1内で直接throwされた時は、一番外側のcatch1ブロックへ、
try2内で直接throwされた時は、catch2ブロックへ処理が移ります。
次に、例外3が発生した場合は、catch2ブロック内のif文に引っかかり、
throwされていますが、”throw”と言う記述だけです。
この記述は、例外の再スローと言い、
1つ上位のcatch1ブロックへ処理が移ります。
この時には、再スローされたcatch2ブロック内で取得していた
例外用オブジェクトも渡されます。
例外の再スローは、現在の場所(catch2ブロック内)では
例外処理に対応するのに必要な情報が足りない。と言った場合などに用いられます。
例外指定子
try~catchブロックでは様々なデータ型によるthrowを行えますが、
例外指定子と言う機能を使うと、指定されたデータ型以外のthrowはできなくなります。
また、例外指定子は関数単位で設定します。
class MyException extends Exception { }
public class Cmain {
public static void main(String[] args) {
test();
}
static void test() throws (MyException) {
try {
if (例外1) throw new MyException();
}
catch (MyException e) {
System.out.println("MyException型の例外が発生");
}
}
}
このプログラムではtest関数に例外指定子を指定しています。
この場合、throws (MyException)となっているので、
test関数内でthrowできる例外用オブジェクトは、
MyException型のみとなります。
それ以外の例外用オブジェクトによるthrowはできません。
また、複数の例外用オブジェクト型を指定する事も可能です。
static void test() throws (MyExceptino, MyException2);
例外指定子はバグ発生率の低下等に活躍し、
プログラムを、ある程度分かりやすくする効果があります。
また、第3者が作った例外指定子付きの関数を使う場合で、
その関数の例外処理を行う場合は、
記述されている例外指定子の例外用オブジェクトだけに対応する例外処理を書けば良い。
と言う意味で受け取れるので、分かりやすいと思います。
例えば、下の例だとtest関数はMyException型とMyException2型を例外として
発生させる可能性があるので、
MyException型とMyException2型を引数としたcatchブロックだけを
記述すれば例外処理できる。と言った具合です。
static void test() throws (MyException, MyException2);