構造化例外処理

「構造化例外処理」の構文はtry-catch-finallyの三つのブロックで構成され、tryブロックに行いたい処理、catchブロックに処理によって発生する可能性のある例外の回復処理、そしてfinallyブロックに例外発生の有無にかかわらず実行したい処理を記述します。

書式

	try {
		行いたい処理;
	} catch (例外クラスの型 例外クラスの変数) {
		例外の回復処理;
	} fainally {
		例外発生の有無にかかわらず実行したい処理;
	}

さらに例外が発生した場合、その発生したメソッド内でキャッチして例外を処理することも、メソッド名に続けてthrows宣言を行い、例外処理をメソッドの呼び出し元に移譲することも出来ます。この場合、そのメソッド内でのその例外に対する例外処理は不要です。
なお、例外処理の移譲によってメソッド呼び出しのおお元で例外を一元管理して処理することが出来ます。

書式

戻り値の型	メソッド名(引数並び) <strong>throws 例外クラスの型</strong> { }

除算によりArithmeticException(算術例外)が発生しても自分では例外処理をせず、呼び出しもとに例外処理を移譲する

int Divide(int num, int denum) throws ArithmeticException {
	return (num / denum);
}

try ブロック内には、複数の文を記述できます。もし例外が発生したらtryブロック内の残りの処理は全て読み飛ばされ、対応するcatchブロックに制御が移ります
catchブロックの目的はプログラムを正常な状態に復帰させることで、キャッチできる例外はすべての例外のルートクラスであるThrowableクラス、例外処理が必須のExceptionクラスだけでなく、例外処理が必須ではないErrorクラスやRuntimeExceptionクラスもキャッチできます。catchブロックの処理が終了すると「不具合は対処された」として、finallyブロックの処理に移ります
finallyブロックは必ず実行されるブロックであり、例外をthrows宣言している場合、例外が移譲される前にfinallyブロックが実行されます。またcatchブロックにreturn文がある場合でも、return処理の前にfinallyブロックが実行され、その後catchブロックに戻りreturnが実行されます。

try-catch-finallyの構文は、各ブロックの順序を変更することはできません。誤った順序で記述するとコンパイルエラーになります。tryブロックとfinallyブロックは1つずつしか記述できず、複数記述するとコンパイルエラーになります。一方、catchブロックは複数記述できます。

なお、catchブロックを複数記述する場合はcatchする例外の順序はサブクラスを先頭にする必要があります。これは例外クラスのインスタンスも、ほかのクラスと同様にポリモーフィズムが成り立ち、サブクラスの例外クラスのcatchはスーパークラスの例外処理がcatch可能であり、結果サブクラスの例外処理がスーパークラスの例外処理の後に記述されていると、サブクラスの例外処理は常に実行されない事になるからです。このような場合、コンパイラーは到達不可能なコードをあるとして、コンパイルエラーを発生します。

Exceptionクラスのサブクラスとして独自例外を作る

public class SubException extends Exception { }

Exceptionのcatch文を先に記述

public class CatchOrder {
	public static void main(String[] args) {
		try {
			throw new SubException();
		} catch (Exception e) {
			
		} catch (SubException e) {
			
		}
	}
}

SubExceptionの例外もExceptionのcatch文が取ってしまうのでSubExceptionのcatch文は到達不可能なコードとなりコンパイルエラーになります。

>javac CatchOrder.java
CatchOrder.java:7: エラー: 例外SubExceptionはすでに捕捉されています
                } catch (SubException e) {
                  ^
エラー1個

>

SubExceptionのcatchブロックはExceptionのcatchブロックより前に置きます。

複数のtry-catchがネストしている場合、スローされた例外を受け取るのは、その例外に対応したもっとも近いcatch ブロックです。ネスト階層ごとにfinallyブロックがある場合は、ネストの内側から順にすべてのfinallyブロックが実行されます

例外処理に戻る

ErrorとException

Javaでは例外処理の対象はError、Exceptionの2種類です。
さらに詳しく言えばExceptionはコンパイラーが例外処理が記述されていることをチャックする検査例外であるのに対し、ExceptionのサブクラスであるRuntimeExceptionは、例外自体が発生しないようにプログラマがロジックを工夫することが期待されているためコンパイラーは例外処理の記述をチェックしない事。
そして、例外処理の記述法などを説明しました。

ここでは、それらError、ExceptionとしてJavaが用意している例外クラスを説明し、さらにそれらを踏まえて独自に例外クラスを作って例外処理する方法を説明します。

Error
トラブルが発生した場合、JVMがErrorクラスまたは、そのサブクラスのインスタンスを生成しプログラムに通知します。プログラム処理ではこの種のトラブルは復旧出来ないので例外処理の記述は不要ですが、例外処理を記述してプログラムが強制終了させずに、トラブルが発生したことをユーザに知らせ、プログラムを正常終了させることはできます。
基本的には、このような事態が発生しないようにプログラムを十分テストしたり、実行環境を確認し、整えておく必要があります。
NoClassDefFoundError
JVMが実行対象のクラスファイルを発見できなかった。
コンパイル時点で存在していたクラスが、見つからない。
VirtualMachineError
JVMが壊れているか、または動作を継続するのに必要なリソースが足りなくなった。
StackOverflowError
JVMが「再帰呼び出し」などで、スタック領域の不足を検出した。
OutOfMemoryError
JVMがインスタンスを保存したり、クラスの定義情報を保存したりするヒープ領域が一杯になり、新しいオブジェクトを割り当てることができない。
Internal Error
JVM内で何らかの内部エラーが発生した。
ExceptionInInitializerError
JVMがstaticイニシャライザを処理している間に例外が発生(NullPointerExceptionなど)したとき、例外の通知相手がまだ存在しないのでErrorを発生させた。
Exception
プログラム処理による復旧が可能なで例外であり例外処理の記述が必須です。コンパイラーが例外処理の記述をチャックするので例外処理の記述がないとコンパイルが完了しません。
ClassNotFoundException
クラスの文字列名を使用してforName、findSystemClass、loadClassメソッドでロードしようとしたが、指定された名前のクラスの定義が見つからなかった。
IOException
入出力処理の失敗、または割り込みの発生による例外
FileNotFoundException
指定されたパス名で示されるファイルが開けなかった
SQLException
データベース操作時に発生した例外
RuntimeException
プログラム時の考慮によって回避できる例外です。コンパイラーは必要な考慮がされていることを前提に例外処理の有無をチャックしません。従って、プログラム上の考慮が不足していてもコンパイルは通ってしまうので、実行時に例外が発生する可能性があります。
ArithmeticException
ゼロ割等、不能演算が指示された
IllegalArgumentException
利用される側のオブジェクトが不正な引数を受取った
NumberFormatException
parselntメソッドが変換形式に合わない引数を受取った
例えば、Integerクラスのparselntメソッドに文字として英数字以外を渡す。
IndexOutOfBoundsException
配列や文字列、コレクションの範囲外アクセス
ArrayIndexOutOfBoundsException
配列の範囲外アクセス
public class Main01 {
	public static void main(String[] args) {
		String[] str = new String[1];
		for(String s :str) {
			System.out.println(s + " , " + str[1]);
		}
	}
}
>java Main01
Exception in thread &quot;main&quot; java.lang.ArrayIndexOutOfBoundsException: 1
        at Main01.main(Main01.java:5)

>
StringlndexOutOfBoundsException
文字列の範囲外アクセス
public class Main03 {
	public static void main(String[] args) {
		String str = "string";
		System.out.println(str.charAt(6));
	}
}
>java Main03
Exception in thread &quot;main&quot; java.lang.StringIndexOutOfBoundsException: String index out of range: 6
        at java.lang.String.charAt(Unknown Source)
        at Main03.main(Main03.java:4)

>
NullPointerException
オブジェクト参照がnullの状態で、インスタンスにアクセスする。
配列変数がnullなのに、配列のサイズを調べる。
public class Main02 {
	public static void main(String[] args) {
		String[][] str = null;
		System.out.println(str.length);
	}
}
>java Main02
Exception in thread &quot;main&quot; java.lang.NullPointerException
        at Main02.main(Main02.java:4)

>
DateTimeException
Java SE 8で追加されたjava.time.LocalDateクラスでは従来のjava.util.Dateやjava.util.Calendarと異なり月は1から始まります。このため月に0を指定すると、構文上の誤りではないのでコンパイルエラーは発生せず実行時にこの例外が発生します。
ClassCastException
継承関係や実現関係にない型に互換性のないクラスをキャストしようとした。
IlegalStateException
利用される側のオブジェクトが、まだ利用するための準備が終わっていない。

以上はJavaが用意している例外クラスの一部です。その他の例外クラスについてはJava APIドキュメントで確認することが出来ます。

この様に数々の例外クラスが用意されていますが、プログラム開発上独自に値の確認をしたい変数などあるでしょう。これらの確認に自分で独自の例外を定義して、例外処理の仕組みを使えばスマートに不具合への対処を組み込むことが出来ます。

参考例として、映画館のチケットの代金を計算してみましょう。
通常料金は1800円として、夜10:00(22:00)からをレイトショーとして1300円に割引します。
上映は朝9:00から始まるとして、希望の上映開始時間を整数で入力してもらいます。
入力された数字が9より少なかったり、24以上だったりした場合は再入力。また、数字以外の文字が入力された場合も再入力してもらいます。
時間の入力範囲チェックにはRuntimeExceptionのサブクラスとして独自例外:OutOfTimeRangeExceptionを作成し、9~23以外の入力があった場合にそれを発行し、数字以外の誤文字入力は実行時例外として通知されるNumberFormatExceptionをメインクラスでそれぞれ例外処理しています。

独自例外OutOfTimeRangeException

public class OutOfTimeRangeException extends RuntimeException { }

レイトショー割引も考慮してチケット代金を求めるHowMuchIsTheFee

import java.util.Scanner;

public class HowMuchIsTheFee {

	public static void main(String[] args) {
		
		HowMuchIsTheFee hmticket = new HowMuchIsTheFee();
		
		int ticketFee = 1800;
		boolean lateShow = hmticket.isLateShow();

		System.out.print("チケット代は");
		if( lateShow ) {
			System.out.print("レイトショー割引で");
			ticketFee = 1300;
		}
		System.out.println(ticketFee + "円です。");
	}
	public boolean isLateShow() {
    //22時以降はレイトショーで割引あり。
		while( true ) {
			Scanner sc = new Scanner(System.in);
			System.out.print("購入するチケットは何時の回ですか?\n9以上24より少ない数字を入力してください。:");
			String keyString = sc.next();
			try {
				int ticketTime = Integer.parseInt(keyString);
				if(ticketTime<9 || ticketTime>=24) throw new OutOfTimeRangeException();
				if(ticketTime>=22 && ticketTime<24) return true;
				return false;
			}
			catch (NumberFormatException e) {
				System.out.print("整数値以外の入力がありました。");
			}
			catch (OutOfTimeRangeException e) {
				System.out.print("入力された数字に誤りがあります。");
			}
			finally {
				System.out.println("再入力してください。\n");
			}
		}
	}
}

ハイライトされたisLateShowメソッドが、希望の上映開始時間を受付し、入力値チェックを行い入力誤りがあれば例外処理の中でエラーメッセージを出し、入力に誤りが無ければレイトショーに該当するか否かを論理値としています。
実行結果は以下のようになります。

>java HowMuchIsTheFee
購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:aa
整数値以外の入力がありました。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:8
入力された数字に誤りがあります。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:24
入力された数字に誤りがあります。再入力してください。

購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:10
再入力してください。

チケット代は1800円です。

>java HowMuchIsTheFee
購入するチケットは何時の回ですか?
9以上24より少ない数字を入力してください。:23
再入力してください。

チケット代はレイトショー割引で1300円です。

>

例外処理に戻る