例外処理
「想定外の動作が起こったときの対応を、あらかじめ指定しておくこと」
【説明】 例外とは
例外の発生する原因として、例えば
- ファイルの終了検出
- エラーの発生
- 0除算の実行
といったなど、通常ではない特別の状態のときに発生します
例外はランタイムシステムが投げる場合もありますし、 自分で例外オブジェクトを作って投げる (throw する) こともできます。
【説明】 例外処理とは
例外が起こったときの対応を、あらかじめ指定しておくことを例外処理といいます。
通常の条件分岐(if文や case文) をつかようより 例外処理を使うことによって、プログラムの構造がシンプルでわかりやすものになる。
スタックトレース (stack traceback)
スタックトレースとは、実行中のコンピュータプログラムにエラーが発生した際に、直前に実行していた関数やメソッドなどの履歴を表示すること。
例外処理を行う方法
例外処理は以下の2つの方法があります。
1. 自分で例外を処理する
自分で例外を処理する場合は、try – catch – finally構文を使います。
finallyブロックは必要に応じて記述しましょう。
例外が発生する可能性のあるメソッドをtryブロックで囲み、catchブロックで処理したい例外を捕まえます。finallyブロックは、例外が発生してもしなくても共通で行う処理を書きます。
2. 自分では処理せず、例外処理を呼び出し元のクラスにスルーする
「例外処理を自分自身で行うのではなく、呼び出し元のメソッドでやってもらう」こともできます。
メソッドのシグニチャに以下の構文で記述することで呼び出し元に例外処理を丸投げできます。
throws 例外クラス名
例外が投げられたら、Java のランタイムシステムはその例外の種類に応じた 例外ハンドラ を探します。 例外ハンドラが見つかれば、そのハンドラが実行されます。
例外ハンドラが見つからない場合は、Java ランタイムはデフォルトの例外ハンドラを実行します。 デフォルトのハンドラではスタックトレースを出力して、プログラムを終了します。
例外の種類
例外には大きく分けて、非検査例外と検査例外に分かれます。
そして、非検査例外の中にも「実行時例外」と「エラー」があります。
主にその3種類があり、それぞれ内容と処理の仕方が異なります。
非チェック例外 (非検査例外, unchecked exception)
非検査例外は、例外処理を記述したかどうかをコンパイラが検査しない例外を指します。
非チェック例外には、次の2種類があります。
- 例外処理を記述してもプログラムレベルでは回復できないような重大な例外
- 頻繁に発生するので、いちいちtry/catchするのが効率的でない例外
1の場合はその例外を処理してどうこうする、というのはあまり期待されていなくて、速やかにプログラムをシャットダウンするべきというものです。
2のの場合は、たとえば「宣言した範囲を越えて配列をアクセスした」ときにArrayIndexOutOfBoundsException
という例外が発行されますが、この例外の発生に備えようとすると、配列を扱うすべての処理をtryブロックで囲まねばならず、これはそもそも「プログラムが間違ってる」ので例外処理などせずに、さっさと実行を止め手書き直したほうがいいよね、って話です。
Exceptionのサブクラスでも、RuntimeExceptionを継承していれば非検査例外として扱われ、try-catchなどの例外処理を記載してももちろん問題ないが、基本的に「例外処理は不要」になる。
非チェック例外の中でも「実行事例外」と「エラー」の2タイプに分かれます。
・ 実行時例外(RuntimeException)
null の参照や配列のインデックス外アクセスなど、プログラムの不具合に起因する例外。
キャッチすることも一応できるが、本来は不具合のほうを直すべき。
・ エラー
メモリ不足や「あるファイルを開いて、文字を読み込む」ということを想定しているときに、 ファイルが存在しないとか、ファイルのアクセス権の設定がでていていなくて、読み込めないとか、そういう場合の例外。
ちなみに、エラーは必ずしも例外で検出しないといけないことはなくて、状況によっては普通に関数の戻り値がこれこれだったら、エラーとする、という風にもできます。
【代表的な非検査例外】
例外の名前 | 概要 |
---|---|
NullPointerException | オブジェクトが必要な場合に null に対して操作を試みるなどした場合に発生する例外。(通称ヌルポ) |
IndexOutOfBoundsException | 配列のインデックスなどのインデックスについて、有効な範囲を超えてアクセスした場合に発生する例外。 |
チェック例外 (検査例外, checked exception)
検査例外とは、例外処理を記述したかをコンパイラが検査する例外を指します。
チェック例外というのは、ファイルを開こうとしたらそのファイルがなかったとか、プログラムの不具合とは関係なく、 状況によっては発生しうる状況とされるものです。
検査例外のクラスであるExceptionを継承している例外クラスは、try-catchやthrows句で宣言するなどの「例外処理が必須」になる。
ここで言う検査とは、catchやthrowのこと。検査例外を検査していない場合はコンパイルエラーになる。
【代表的な検査例外】
名前 | 概要 |
---|---|
IOException | I/O (入出力) 関連のなんらかの例外が発生したことを示す汎用の例外クラス。 |
ParseException | パース (文字解析) 中になんらかの例外が発生したことを示す例外クラス。 |
SQLException | データベース関連の例外が発生したことを示すための例外クラス。 |
SOAPException | SOAP 関連 (いわゆる XML Web サービス) に関連してなんらかの問題が発生したことを示す例外クラス。 |
TimeoutException | ブロッキング操作 (処理の結果を待つ待ち状態) がタイムアウトによって失敗したことを示す例外クラス。 |
【例外処理の基本形】 try-catch ブロック
以下が例外処理の基本的な構文です。try、catch、finallyはこの順序で連続して記述する必要があります。 tryとcatchだけ、またはtryとfinallyだけでもかまいません。
try {
// 例外が発生する可能性のある処理
} catch (例外クラスA インスタンス名A) {
// 対策処理A
} catch (例外クラスB インスタンス名B) {
// 対策処理B
} finally {
// 例外が発生するしないに関わらず必ず実行する後処理
}
try(例外の発生する可能性のある処理を記述)
Javaの標準APIにおいては、予め例外処理の発生する可能性のあるメソッドに対しては、
throws ◯◯Exception と定義されています。
catch(発生した例外をキャッチ)
複数記述することが可能です。
発生する例外によって対策処理を分ける場合などに有効です。
catch では、発生した例外がどんな内容の例外であるかをコンソールへログ出力することが多いです。
通常の処理の道程に加え、例外もログ出力することで保守体制を整えます。
その他には、例外の情報を一時的に保持しておいて、
finally へ処理が移行した際に、その保持していた内容を使用して何かの処理を実行する。
といった感じです。
finally(例外発生の有無に関わらず実行される)
finally に関しては、 必須ではありません。
しかし、例外の発生有無に関わらず確実に処理を実行するので、
〆となる終了処理を追加したい場合 は必要に応じて実装すればよい、といった感じです。
【サンプル.java】
下記は例外内容によってそれぞれ違うメッセージを出力する例外処理です、
今回はわざと null を触り、ランタイムに NullPointerException 例外を投げさせています。
public class Main {
public static void main(String[] args) {
try {
String s = null; // 値に nullを格納
System.out.println(s.toUpperCase());
} catch (IndexOutOfBoundsException e) {
System.out.println("範囲外の例外です。");// 有効な範囲を超えてアクセスした場合
} catch (NullPointerException e) {
System.out.println("ヌルポです。"); // 値がnullだった場合
System.out.println(e);
} catch (Exception e) {
System.out.println("それ以外の例外です。");//不明の例外
}
}
}
【実行結果】
ヌルポです。
java.lang.NullPointerException
throwステートメント / throwsステートメント
どちらも意図的に例外処理を発生させる機能 です。
「throw」を使用すると任意のタイミングで例外を発生させて、例外処理を行うことができます。
「例外処理(tryブロック、catchブロック、finallyブロック)」、または、 throws を記述し対応する必要があります。
throwステートメント
「throw」を使用すると任意のタイミングで例外を発生させて、例外処理を行うことができます。
throwステートメントは以下のように記述します。
throw new 例外クラス
例えばある変数の値が指定した範囲にないときに必ずエラーにしたいとします。
上記の例では様にしています。
【サンプル】 iが負の数の場合に例外を発生させる
この場合はExceptionクラスを使用しています。例外の発生後はこのthrowステートメントに対応するcatch節に処理が移ります。
try{
if(i < 0){
throw new Exception("iが指定範囲を超えています");
}
}
catch(Exception e){
e.printStackTrace();//throwの後ここに処理が移る
}
throws
通常、throw されている場合は、 try-catch により、例外を補足し対応しますが、数のメソッドを使用した処理を書く際、例外処理を各メソッドの中ではなく呼び出し元でまとめて行いたい場合があります。
そういった場合、「例外を投げる可能性があるメソッド」に対して宣言時に 「throws」を加えることで例外処理を呼び出し元に移譲することができます。
例外処理は突き詰めるとかなりの量を記述する必要があります。
そのため、必要以上に捕捉するような不要な例外処理は極力避けた可読性の高いコードとなる方が好まれます。
例外がthrowされるメソッドより 呼び出し元処理で例外を捕捉することで、処理が簡潔になり、同じような例外処理が発生するメソッドが複数存在した場合、例外処理が必要な箇所を絞り込んだ対応ができます。
【サンプル.java】
package about_exception;
public class Main {
private static final String PASSWORD = "password";
public static void main(String args[]) {
String password = PASSWORD;
String inputPassword = "passsswooorrddd";// 入力されたパスワード
try {
if (!password.equals(PASSWORD)) {
throw new Exception("パスワードが間違っています");
}
System.out.println("入力された " + inputPassword + " はパスワードと一致しません");// 例外でthrowされた以降の処理は実行されない
} catch (Exception e) {
System.out.println("パスワードが間違っています");
System.out.println(e);
}
}
}
【実行結果】
入力された passsswooorrddd はパスワードと一致しません