- トップページ
- 特集PC技術
- Java言語入門 ~C言語を学んだ君へ~
- [第13回]例外処理
Java言語入門 ~C言語を学んだ君へ~
[第13回]例外処理
Javaは、異常事態を2つの種類に分けて扱います。
その異常事態とは「エラー」と「例外」です。
何か似ている2つの言葉ですが、何が違うのか見ていきましょう。
目次
- [1]Javaから見たエラーと例外
- [2] 例外処理に関する用語
- [3] 例外処理の基本 try-catch-finally
- [4] 複数書いた場合の try-catch-finally
- [5] 意図的に例外を発生する throw
- [6] 例外の情報をわかりやすくする throws
- [7] 例外の情報
- [8]練習問題 第1問
- [9] 練習問題 第2問
- [10] 練習問題 第2問解答
[1]Javaから見たエラーと例外
「エラー」と「例外」の違いは、プログラムで対処できるかできないかです。
エラーの場合はプログラムで対処できないもの、例外の場合はプログラムで対処できるものです。
エラーの例としては、「メモリ不足」が挙げられます。
例外の例としては、「整数を0で割り算した」「配列の要素より大きい要素にアクセスした」が挙げられます。
例外処理をした場合としていない場合の比較
例外処理をすることで、以下のようなメリットがあります。
・ 呼び出し元の指定した場所に制御を1度にうつし、例外の情報を返す
・ 1箇所に例外に対する方法をまとめる
例外を使わない場合に、処理をする方法として、if文が思いつくと思います。
しかし、if文では必要な個所に何度も書く必要があり、とても大変です。
そこで、例外処理の出番となります。
例外処理を使うことで一元管理できるわけです。
例外を処理した場合には、すぐにプログラムを止めず、どこでどのような異常事態が発生したのかを通知してからプログラムを止めます。
[2] 例外処理に関する用語
ここでは、Javaプログラムで使う例外処理に関する用語の説明をします。
例外処理のクラス
種類 | クラス名 | 特徴 |
---|---|---|
エラー | Java.lang.Error | 致命的な異常事態 |
例外 | Java.lang.Exception | 一般的な異常事態 |
javaプログラムでは、その異常事態に応じて、定義されている特別なクラスのインスタンスを生成します。
上記の表は、その代表的なクラスになります。
このクラスの中には様々なサブクラスが存在しています。
では、実際に例外クラスのサブクラスにはどのような種類があるのか見ていきましょう。
例外を扱うクラス群
クラス名 | 機能 |
---|---|
ClassNotFoundException | クラスが見つからない |
IllegalAccessException | クラスに不正アクセスした |
InstantiationException | インタフェース、抽象クラスをインスタンス化 |
InterruptedException | スレッドに割り込み |
NoSuchFiledException | フィールドが見つからない |
NoSuchMethodException | メソッドが見つからない |
RuntimeException | 実行時に例外が発生 |
IOException | 入出力の例外が発生 |
FileNotFoundException | ファイルが見つからない |
NumberFormatException | 不適切な文字列を数値に変換 |
ArrayIndexOutOfBoundsException | 配列の範囲外を指定 |
ArithmeticException | 計算により例外が発生 |
NullpointerException | nullオブジェクトにアクセス |
ここで、すべてを覚える必要はありません。
必要な時に必要なクラスを調べて使えばよいです。
例外クラスは「Exception」とついているのでわかりやすいと思います。
例外の種類
例外にはさらに2種類があります。
・ 例外処理を記述しなくてもコンパイルできる例外
・ 例外処理を記述しないとコンパイルエラーになる例外
コンパイルできる例外は、RuntimeExceptionクラス、またはErrorクラスのサブクラスです。
コンパイルできない例外は、上記以外のクラスです。
例外処理の書き方には注意しましょう。
例外処理のキーワード
例外処理のキーワードはこのようなものがあります。
・ try
・ catch
・ finally
・ throw
・ throws
次から、実際にプログラムを書き、説明をしていきます
[3] 例外処理の基本 try-catch-finally
まずは、例外処理のもっとも基本的なtry-catch-finallyを見ていきましょう。
try-catchは例外が発生した場合にプログラムが終了することを防ぎます。
try-catch-finallyの形式
try {
例外が発生する可能性のある処理
} catch (発生した例外クラス 例外の変数名) {
例外ハンドラ
} catch (発生した例外クラス 例外の変数名){
例外ハンドラ
}finally{
最後に必ずしたい処理
}
tryブロックの中に「例外が発生する可能性の処理」を書きます。
例外とはプログラムで対処できる異常事態のことでした。
0で割り算をした、配列の範囲外を指定した等です。
catchブロックの中の引数には、tryブロックで起きる例外処理の種類を書きます。
発生した例外クラスは前ページで紹介したものです。
計算の例外が起きたら、ArithmeticException
配列の範囲外が指定されたら、ArrayIndexOutOfBoundsException
と、使い分けます。
例外の変数名はローカル変数なので、名前はなんでも構いません。
catchブロックの中身には例外が発生した場合に、実行される処理の内容を記述します。
発生した例外とは、引数に指定した例外のみです。
例外ハンドラとは、例外が発生した時の処理のことです。
finallyブロックに書かれた記述は例外が発生しても発生しなくても実行される処理です。
try-catchのルール
・ try1つにつき、catchかfinallyのどちらかを最低1つつけること
・ catchはいくつでもつけることができる
・ finallyをつけるブロックは1つのみ
・ catchの処理順番はif文と同じように上から順番に判断する
・ catchの引数は、サブクラスを先に書き、スーパークラスは後に書く
try-catch-finallyを使う上でこれだけは覚えておいてください。
では、実際にtry-catch-finallyを使ったプログラムを見てみましょう。
try-catch-finallyのサンプルプログラム
public class Java13_01{ public static void main(String args[]){ func(); } public static void func(){ int array[] = new int[5]; try { for(int i = 0; i <= 10; i++){ array[i] = i; System.out.println(array[i]); } }catch(ArrayIndexOutOfBoundsException e) { System.out.println("例外"+e); }finally{ System.out.println("例外チェックが終わりました"); } } }
実行結果
このプログラムでは、配列の要素が5なのにそれ以上のアクセスしたために例外が起きています。
配列の範囲外を指定しているので、ArrayIndexOutOfBoundsExceptionを使っています。
このプログラムを変更して以下のように書くこともできます。
少し変更したプログラム
public class Java13_01{ public static void main(String args[]){ try { func(); }catch(ArrayIndexOutOfBoundsException e) { System.out.println("例外"+e); }finally{ System.out.println("例外チェックが終わりました"); } } public static void func(){ int array[] = new int[5]; for(int i = 0; i <= 10; i++){ array[i] = i; System.out.println(array[i]); } } }
実行結果
同じ結果になりました。
呼び出し元で、try-catch-finallyを使ってメソッドを呼び出したときにも、同じように処理することができます。
これは、例外が発生した時に呼び出し元に自動的に戻り処理を見ているからです。
しかし、1つだけだとその様子があまりわからないと思います。
[4] 複数書いた場合の try-catch-finally
少し複雑なtry-catch-finally処理を見ていきます。
今回のプログラムはメソッドを次々と呼び出しています。実際にどのような処理が行われているのかを考えてみてください。
少し複雑なtry-catch-finallyのプログラム
public class Java13_02{ public static void main(String args[]){ try{ System.out.println("main処理開始"); meth1(); System.out.println("main処理終了"); }catch(Exception e){ System.out.println("mainで例外発生:"+e); } finally{ System.out.println("mainの例外チェック終了"); } } static void meth1(){ try{ System.out.println("meth1処理開始"); meth2(); System.out.println("meth1処理終了"); }catch(ArrayIndexOutOfBoundsException e){ System.out.println("配列の要素外にアクセス:"+e); } finally{ System.out.println("meth1の例外チェック終了"); } } static void meth2(){ try{ System.out.println("meth2処理開始"); meth3(); System.out.println("meth2処理終了"); }catch(NullPointerException e){ System.out.println("空のインスタンスにアクセス:"+e); } finally{ System.out.println("meth2の例外チェック終了"); } } static void meth3(){ try{ System.out.println("meth3処理開始"); int array[] = new int[3]; array[4] = 10; meth4(); System.out.println("meth3処理終了"); }catch(ArithmeticException e){ System.out.println("計算の例外が発生:"+e); } finally{ System.out.println("meth3の例外チェック終了"); } } static void meth4(){ System.out.println("meth4の実行"); } }
実行結果
どうでしょうか。このような処理になりましたが、予想できましたか。
meth3()メソッド内で、配列要素範囲外による例外が発生しています。
まず、meth3()メソッドのcatchを見ますが、計算例外の場合の処理です。
実際に発生した例外は配列要素範囲外なので違います。
そこで、meth3()メソッドを呼び出したmeth2()メソッドに戻ります。
その前に、meth3()メソッドにfinallyがあり、先にfinallyブロック内の処理が行われます。
次にmeth2()メソッドでcatchをしてみます。
ここでは、空のインスタンスにアクセスしたかどうか見ています。
これも配列要素範囲外指定の例外処理ではありません。
さらに、この処理を飛ばし、meth2()メソッドを呼び出しているmeth1()メソッドに行きます。
meth1()メソッドのcatchを見てみると、配列要素範囲外の例外が書いてありますので catch内の処理を行います。
その後は次々と処理を呼び出し元に返し、プログラムが終了となります。
このプログラムから学んでほしいことは、
・ finallyがいつ実行されるか
・ 例外処理が見つからない時は呼び出し元に返していく
ということです。
[5] 意図的に例外を発生する throw
プログラムの結果によって例外処理の方法を見てきました。
次に説明するのは、throwです。
throwというキーワードを用いると意図的に起こしたい例外を起こすことができます。
また、throwで例外を起こすことを「例外を投げる」とか、「スローする」といいます。
意図的に起こすなんてただの嫌がらせをしたいわけではありません。
どのようにやるのか見ていきましょう。
throwの形式
throw 例外インスタンス
throw new 例外インスタンス(引数);
基本は1つめの書き方を使います。
2つ目の書き方は1度だけ使いたい場合に使います。
throwは検出したメソッド内で処理をしたくない場合に用います。
これを使うことで、呼び出し元でその例外処理のお願いをするわけです。
では、2つ目の書き方を元に書き方を見ましょう。
サンプルプログラム
public class Java13_03{ public static void main(String args[]){ try{ meth1(); }catch (NullPointerException e){ System.out.println("例外処理が発生:"+e.getMessage()); } } public static void meth1(){ throw new NullPointerException ("テスト"); } }
実行結果
mainでmeth1()メソッドを実行し、throwで処理を返しています。
throw new 例外インスタンス(引数);で行っている時は、
getMessage()メソッドを使うと引数の中身を表示することができます。
[5] 例外の情報をわかりやすくする throws
次にthrowと少し似ているthrowsについて説明します。
throwsはメソッドの宣言で使います。
throwsの形式
修飾子 戻り値 メソッド名(引数)throws 例外クラス名{..}
細かい説明する前にプログラムで見たほうがわかりやすいと思います。
次のサンプルプログラムを見てください。
サンプルプログラム
public class Java13_04{ public static void main(String args[]){ try{ meth1(); }catch (NullPointerException e){ System.out.println("例外処理が発生:"+e.getMessage()); } } public static void meth1()throws NullPointerException{ throw new NullPointerException ("テスト"); } }
実行結果
meth1()メソッドで起こる例外はNullPointerExceptionです。
そのため、呼び出し元のメソッド宣言にthrows NullPointerExceptionをつけます。
このメソッドの呼び出し元で、NullPointerExceptionの例外処理を書きます。
ここで、何かおかしなことを感じませんか。
これは少し前に書いたプログラムと全く変化がありません。
つまり、throwsをつけてもつけなくても処理内容は全く同じなのです。
では、どうしてthrowsというものをつけるのでしょうか。
throwsのメリット
・ メソッド内を見なくてもあらかじめ発生する例外に対して記述ができる
・ つけることにより、このメソッドでどのような例外がありえるのかが理解ができる
メソッドの内部も隅々までみなくても、メソッドの宣言部分を見るだけで
どの例外処理を準備すればいいのか一目瞭然になるわけです。
throwsはただ、このメソッド内で発生する可能性のある例外の情報をわかりやすくしているだけなのです。
[7] 例外の情報
例外の情報では、例外の情報をどのように見るのでしょうか。
確かに出力してみればいいでしょうが、もう少し効率のいい方法が欲しいです。
少し前にgetMessage()を使いました。
これが、例外の情報を詳しく見る方法なのです。
例外の情報を見るメソッド
メソッド | 機能 |
---|---|
printStackTrace() | 例外の原因を調べる |
toString() | 例外の情報を文字列として習得 |
getMessage() | コンストラクタで引数とした文字を取得 |
このような例外情報を見るメソッドはプログラムのミスを探す場合に大いに役に立ちます。
[8]練習問題 第1問
以下の設問について答えなさい。
設問1
1.例外とエラーの違いをJavaではどのように区別しているか答えなさい。
設問1の解答・解説
Javaでは、例外とエラーの2つの扱いが少しだけ異なっています。
例外はプログラムで対処できる。
エラーは致命的なエラーです。
メモリの不足などのことを指しています。
設問2
2.以下のようにtry-catch文を書いた。しかし、これは正しくない。その理由を答えなさい。
設問2の解答・解説
catchは上から順番に処理されていくので、 最も広範囲の例外処理を行うExceptionクラスは後に書くべきです。
Exceptionクラスは全ての例外のスーパークラスになるので最後に書くことになります。
例外クラスの順番には注意しましょう。
設問3
3.throwとthrowsの違いを答えなさい。
設問3の解答・解説
throwは、例外を呼び出し元に投げて、例外処理をしてもらう時に使います。
throwsはメソッドに宣言してこの中で起こりえる例外を通達する働きがあります。
[9]練習問題 第2問
以下に記述してあるプログラムを見て、どのような順番でSystem.out.println()が実行されるのか答えなさい。
[10]第13回 第2問解答
第13回 第2問解説
例外が発生した時にどのように処理するのかという問題でした。
1.Dの表示
main()メソッドにある、func1()メソッドを実行します。
func1()メソッド内でエラーが起きるので、catchブロック内を実行します。
ここで、"D"が表示されます。
2.Eの表示
例外が発生したのでfunc1()メソッドを呼び出したmain()に戻ろうとしますが その前にfunc1()メソッド内にはfinallyブロックがあるので、先にこちらを実行してから戻ります。
"E"が表示されます。
3.Fの表示
戻ったmain内にもfinallyがあるので、実行します。
func2()メソッドを呼び出しています。
func2()メソッドが実行され、tryブロック内の処理が行われます。
"F"が表示されます。
4.Hの表示
catchブロックになる例外はないので、finallyブロックを処理します。
"H"が表示されます。
5.Bの表示
finallyブロックが処理されたあとmain内にあるfinallyの続きが実行されます。
"B"が表示されます。
6.Cの表示
tryブロックの処理が終了したので、tryの外にある"C"が表示されます。
コメント
匿名
「try-catch-finallyのサンプルプログラム」の次の「少し変更したプログラム」のソースにて、
”int array[] = new int[10];”となっているが、
”int array[] = new int[5];”の誤りですね!
2011年2月 2日 13:44
特集PC技術メンバー
ご連絡ありがとうございます。ほぷしぃ特集PC技術メンバーです。
> ”int array[] = new int[10];”となっているが、
> ”int array[] = new int[5];”の誤りですね!
ご指摘の通り、”int array[] = new int[5];”が正しい記述です
上記のようにコンテンツを直しました。
誤っているプログラムソースを載せてしまい、申し訳ありませんでした。
また、何かありましたら、コメント等でご連絡をお願いします。
2011年2月 3日 10:52
ぺ
Java13_02例題の実行結果が、
"meth3"となっていますが、"meth4"ですね。
2011年5月 4日 22:41
特集PC技術メンバー
ご連絡ありがとうございます。ほぷしぃ特集PC技術メンバーです。
> Java13_02例題の実行結果が、
> "meth3"となっていますが、"meth4"ですね。
プログラムコードと実行結果が食い違っていました。
そのため、実行結果に合わせてプログラムソースコードを変更いたしました。
誤っているプログラムソースを載せてしまい、申し訳ありませんでした。
また、何かありましたら、コメント等でご連絡をお願いします。
2011年5月 6日 12:49
コメントの投稿
トラックバックURL
http://www.isl.ne.jp/cgi-bin/mt/mt-tb.cgi/1097