【Java】例外処理、必須処理

2018年3月11日

本稿は例外処理について説明します。

例外は、不正な処理を実行しようとした場合に起こります。

例えば、配列の要素数を3で作成して10番にアクセスしようとした時などです。

例外が発生するとプログラムが停止してしまいます。

今回はその例外を検出し、例外発生時の処理を実行する方法と、明示的に例外を発生させる方法、例外処理後に必ず実行したい処理を定義する方法を見ていきましょう。

良くお目にかかる例外と危険なエラー

頻繁に見ることがある例外をいくつか紹介します。

・NullPointerException
「ぬるぽ」と略してよばれ、そこそこの経験者でも頻繁にお目にかかる例外です。null値に対してメソッド実行や変数アクセスをしようとすると発生します。

・ArrayIndexOutOfBoundsException
配列の範囲外へアクセスすると発生します。初心者のうちはよくお目にかかりますし、経験者でもたまにお目にかかります。

・IOException
ファイル操作や、通信処理に失敗したときなどによく発生する例外です。

・OutOfMemoryError
メモリ使用量が膨大になると発生します。一応例外処理できますが、すぐにまた発生する可能性があるため、絶対に起こらないようにプログラムしましょう。

try~catch文

try~catch文を利用すると例外が発生したときに受け止め、例外処理を実行させることができます。

try{
例外が起こる可能性のある処理
}catch(キャッチする例外){
例外処理
}

と記述しましょう。

それでは、サンプルをご覧ください。

public class Test {
	public static void main(String[] args){
		try{
			int[] nums = new int[3];
			int a = nums[4];
			System.out.println("処理が完了しました。");
		} catch ( ArrayIndexOutOfBoundsException e ) {
			System.out.println("範囲外の要素にアクセスしようとしました。");
		}
	}
}
実行結果

範囲外の要素にアクセスしようとしました。

このプログラムでは大きさ3の配列に対して4番へとアクセスしようとするプログラムです。

変数numsは4番へのアクセスはできないため、例外ArrayIndexOutOfBoundsExceptionが発生します。

それを、catch ( ArrayIndexOutOfBoundsException e )で例外を受け止め、中の処理を実行しています。

eはその例外の変数です。例外変数を利用してjavaエラーメッセージを出力させたりできます。

また複数の例外が発生する場合は以下のようにcatch区を複数配置したり「|」を利用してOR条件にすることができます。

public class Test {
	public static void main(String[] args){
		try{
			String name = null;
			name.equals(100);
			Class.forName("");
			System.out.println("処理が完了しました。");
		} catch ( ArrayIndexOutOfBoundsException | NullPointerException e ) {
			System.out.println("ぬるぽか配列の範囲外にアクセスしようとしました。");
		} catch ( Exception e ) {
			System.out.println("何らかの例外が発生しました。");
		}
	}
}
実行結果

ぬるぽか配列の範囲外にアクセスしようとしました。

例外クラスはExceptionクラスを継承しているため、Exceptionでいろんな例外をキャッチすることができますが、これをいいことに例外のもみ消しをやり始める人がいるので、使用する場合よく考えて使いましょう。

例外をスローする

自ら例外を発生させるたり、呼び出し元のメソッドに例外をスローする(例外を自分で処理せずに呼び出し元に任せる)ことができます。

APIを作成する人などは適切に例外をスローしてあげるほうが良いでしょう。

自ら例外をスローする場合は

throw 例外クラスオブジェクト

と記述します。

例外処理は呼び出し元に任せる場合は、「メソッド名()throws 例外クラス名」と記述しましょう。

ただし、RuntimeExceptionを継承した例外である、ぬるぽ等はthrows宣言を記述しなくても自動でスローされます。

public class Test {
	public static void main(String[] args){
		try{
			process();
		} catch ( Exception e ) {
			System.out.println("エラー");
		}
	}
	
	static void process()throws Exception{
		throw new Exception();
	}
}
実行結果

エラー

例外はExceptionクラス、致命的な例外はErrorクラスを継承することで、自作の例外クラスを作成できます。

また、Throwableを継承しても、同様に例外処理を自作できます。通常はExceptionの継承で作成するのが良いでしょう。

public class Test {
	public static void main(String[] args){
		try{
			throw new OriginalException();
		} catch ( OriginalException e ) {
			System.out.println("エラー");
		}
	}
}
class OriginalException extends Exception{
}
実行結果

エラー

例外処理後に必ず実行したい処理

try~catchを実行した後に必ず実行したい処理を定義することができます。

finally区を用いることで定義できます。

finallyはファイル処理のクローズなど、必ず解放しなければならない処理でよく使用されます。

public class Test {
	public static void main(String[] args){
		try{
			throw new Exception();
			System.out.println("OK");
		} catch ( OriginalException e ) {
			System.out.println("エラー");
		} finally {
			System.out.println("例外が発生してもしなくても実行されます。");
		}
	}
}
実行結果

エラー
例外が発生してもしなくても実行されます。

try with resource

Java7からtry with resourceという便利な機能が提供されるようになりました。

さらにJava9からはtry with resource利用時のクラス宣言がブロック外で可能になりました。

これを利用すると、AutoCloseableインターフェースを実装したクラスは

try(自動でcloseメソッドを呼び出してほしい変数){
}

と定義することで、必ずcloseメソッドが実行されるようになります。

ファイル入出力クラスはほとんどこのインターフェースを実装しているためfinallyよりもこちらの方法のほうが楽ができそうですね。

それではサンプルをご覧ください。

public class Test {
	public static void main(String[] args){
		Gate g = new Gate();
		try(g){
			System.out.println("OK");
		}
	}
}

class Gate implements AutoCloseable{
	public void close(){
		System.out.println("ゲートは閉じました。");
	}
}
実行結果

OK
ゲートは閉じました。