JPIN#8
例外処理
例外処理はトラブル対応と同じ意味になる
プログラムが発生したときに何も反応がないと利用者は困る
そのときにどういった処理をするかを決めるのが例外処理である
プログラムを作った上でどういったトラブルが発生するかを予想して正常に動作させることが重要である
何も動作せずに止まったり落ちることがよくない
どんなトラブルがあるのかを想定する能力が必要である
例外処理とは復帰処理であるともいえる
トラブルが起きたときのプログラムの動き
呼び出したメソッドでトラブルが起きる→処理が正常に終わらないことを伝えなければならない
トラブルのことを例外という
トラブルを報告することをthrowと言う
受け取ることをキャッチ、それに対する対応を例外処理という
例外を報告するのに報告書が必要である
ここにあたるクラスが必要で、その報告書を例外クラスという
つまりまずメソッドが動く→何か処理に対して問題が出る(例外)→例外を呼び出し元へ報告する(throw)→呼び出し元は例外を受け取る(catch)→例外への対応をとる
このような流れになる
報告書の種類
これはライブラリの中にある標準パッケージの中にある
java.lang.Throwableクラスがある
これがスーパークラスに当たる
ここから二つに分かれる
・Exceptionクラス
・Errorクラス
Errorはクラスではなく環境関係のプログラムではどうしようもないものである
例えばメモリが足りないとか、そもそもプログラムを動かすソフトに不具合があるとかそういった事例である
それに対してExceptionクラスはコード上に現れる例外である
String型にint型が入ってしまったり、条件式が矛盾していたりなどである
基本的にはこの二つの報告書の種類に分かれる
例外処理の動き
以下のクラスを見てみよう
public class Main { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ int[] array = new int[0]; array[0] = 10; System.out.println("hello"); } }
このクラスは実行するとエラーになる
それはint型の配列arrayは要素の数が0なのにarrayの0番目に10を代入しようとしているからである
このクラスではJVMにトラブルがスローされてJVMが受け取ることになる
そしてJVMが対応することになる
これをクラス内で処理することができる
そうするには以下のように書く
public class Main2 { public static void main(String[] args) { int[] array = new int[0]; try { array[0] = 10; }catch(ArrayIndexOutOfBoundsException e) { System.out.println("arrayには何も入りません"); } System.out.println("hello"); } }
これで実行すると、
arrayには何も入りません
hello
と出力される
では何をしたのか解説する
まず最初に実行したクラスではjava.lang.ArrayIndexOutOfBoundsExceptionにエラーが発生した
それはもちろん「array[0] = 10;」のコードが問題で出た
これが例外にあたるので例外にあたる部分をtryで囲う
その後に報告書をjava.lang.ArrayIndexOutOfBoundsExceptioncatchをeというインスタンスにしてキャッチしてその処理を{}内に記入している
そしてこのクラスで例外を処理したので”hello”が実行される
ちなみに例外処理が複数ある場合は以下のようにする
public class Main2 { public static void main(String[] args) { int[] array = new int[0]; int a ; try { array[0] = 10; String s = null; System.out.println(s.toUpperCase()); }catch(ArrayIndexOutOfBoundsException | NullPointerException e) { System.out.println("arrayには何も入りません"); } System.out.println("hello"); } }
catchの中でパイプ(|)を使って報告書を付け足せばいい
ちなみに以下のように書くとどうなるのか・・・
public class Main2 { public static void main(String[] args) { int[] array = new int[0]; int a ; try { String s = null; System.out.println(s.toUpperCase()); array[0] = 10; }catch(ArrayIndexOutOfBoundsException e) { System.out.println("arrayには何も入りません"); }catch(NullPointerException e) { System.out.println("ぬるぽ"); } System.out.println("hello"); } }
このように書くとまずプログラムはtryの中を見るときに上から見ていく
そして以下のコードでエラーを見つける
String s = null; System.out.println(s.toUpperCase());
そうするとその下にあるtryのコードは飛ばしてcatchを見にいく
そして
catch(NullPointerException e) { System.out.println("ぬるぽ"); }
ここがその例外処理に当てはまるのでこれを実行してtry-catch構文は終了してしまう
業務トラブル
今まで見てきたのは実行トラブルと言われるものである
これは標準クラスライブラリにあるものである
それ以外のものは自分でクラスから作っていく必要がある
今回はあるクラスに渡す引数がマイナスの時には処理を変えたいときのプログラムを作成する
まずは例外クラスを作る
public class TestException extends Exception{ }
このクラスはEceptionクラスを拡張したクラスになる
次にマイナスの引数を受け取ったときに上記例外クラスに報告するクラスを作る
public class Test { public void execute(int value) throws SampleException {//throwされる可能性を示唆 if(value<0) { SampleException ex = new SampleException(); throw ex; } } }
このTestクラスのexecuteメソッドでは先ほどのSampleExceptionクラスに例外を報告して、引数int型のvalueを受け取るものである
そしてif文で引数がマイナスならSampleExceptionクラスに例外を報告するという流れになっている
if文では例外クラスのインスタンスを生成して「throw ex;」とすることで例外を報告することができる
ここは省略して以下のようにも書くことができる
throw new SampleException(); //SampleException ex = new SampleException(); //throw ex;
この1行で済ませることができる
またこのメソッドは例外クラスに報告するかも知れない可能性があるのでメソッド名の後に「throws 例外クラス」を書く必要がある
もし複数の例外クラスに報告するかも知れない時は「throws 例外クラス①,例外クラス②」と書く必要がある
次にここに引数を渡すmainメソッドを持つクラスを用意する
public class UseTest { public static void main(String[] args) { Test t = new Test(); try { t.execute(-1); System.out.println("safe"); } catch (SampleException e) { //TODO テスト e.printStackTrace();//エラーを出してくれる System.out.println("unsafe"); } } }
ここでテストクラスのインスタンスを作成してそこのexecuteメソッドに-1の引数を渡す
ここで本来は何もエラーは出ないのだが今回は引数がマイナスの時は例外クラスに報告することになっている
よってこの文はコンパイルエラーとなってしまう
そのためにtry-catch構文を使って上記のように囲んで引数がマイナスの時はcatchの後の文を実行するようにしたのである
もしここで引数をプラスにするとexecuteメソッドのif文は作用されないので例外クラスに報告されない
よって"safe"と出力される
ちなみに「e.printStackTrace();」で、生成した例外インスタンスで発生したエラー文を出力することができる
また、ここでも書いているが「//TODO」と書けば後からTODOの部分をチェックする機能がeclipseには備わっている
もし例外クラスが日検査例外に報告するクラスの場合は以下になる
public class SampleException extends RuntimeException{ }
この場合はTestクラスのexecuteメソッドでは例外クラスに報告しなくてもいい
そしてmainメソッドでもtry-catchする必要もなくなる
例外設計
例外処理をするにしても基本的には人間が見える場所で処理をしないと確認することができない
例えば、Aクラスをメインに人間がプログラムを動かしました。AクラスではBクラスのインスタンスを生成してBクラスではCクラスを呼び出して処理をしていました。
Cクラスで例外が発生してしまったときにはその例外をBクラスにthrowする必要がある
そしてBクラスはAクラスにthrowしなければならない
もちろんBクラスで止めて処理することができるがそれでは人間が処理を確認することができない
よって呼び出し元まで例外を持ってくる必要がある
以下のクラスから確認していこう
まずはAクラスが呼び出すBクラス
public class B { public void test() { new C().test(); } }
BクラスではCクラスを呼び出す
次にCクラスを以下のようにする
public class C { public void test()throws SampleException{ throw new SampleException(); } }
Cクラスでは先ほど作った例外クラス(SampleException)にthrowしている
Bクラスに戻ろう
Bクラスでは今のままではCクラスを呼び出すところでエラーになってしまうのでthrowする必要がある
public class B { public void test() throws SampleException{ new C().test(); } }
これでBクラスも例外クラス(SampleException)にthrowした
ではBクラスを呼び出すメインメソッドがあるAクラスを以下のようにする
public class A { public static void main(String[] args) { try{ new B().test(); }catch(SampleException e) { System.out.println("不正な値です"); } } }
メインメソッドではBクラスを呼び出して、それはエラーになるのでtry-catch構文を使って処理している
もちろん処理内容はSampleExceptionである
これで我々人間に例外処理したことがわかるのである