Javaにおける例外処理

導入

例外は、プログラムの実行中に発生し、通常のフローを妨げるエラーイベントです。Javaは、Java例外処理として知られる例外シナリオを処理するための堅牢でオブジェクト指向の方法を提供します。

Javaの例外は、ユーザーが入力した間違ったデータ、ハードウェアの障害、ネットワーク接続の障害、またはダウンしているデータベースサーバーなど、さまざまな状況から発生する可能性があります。特定の例外シナリオで何をするかを指定するコードを例外処理と呼びます。

例外のスローとキャッチ

Javaは、ステートメントの実行中にエラーが発生すると、例外オブジェクトを作成します。例外オブジェクトには、メソッド階層、例外が発生した行番号、および例外のタイプなど、多くのデバッグ情報が含まれています。

メソッドで例外が発生した場合、例外オブジェクトを作成し、ランタイム環境に渡すプロセスは「例外のスロー」と呼ばれます。プログラムの通常のフローが停止し、Javaランタイム環境(JRE)は例外のハンドラを検索しようとします。例外ハンドラは、例外オブジェクトを処理できるコードブロックです。

  • 例外ハンドラを見つけるためのロジックは、エラーが発生したメソッドでの検索から開始されます。
  • 適切なハンドラが見つからない場合、呼び出し元のメソッドに移動します。
  • そして、その繰り返しです。

したがって、メソッドの呼び出しスタックがA->B->Cであり、メソッドCで例外が発生した場合、適切なハンドラの検索はC->B->Aに移動します。

適切な例外ハンドラが見つかると、例外オブジェクトはハンドラに渡され、処理されます。ハンドラは「例外をキャッチする」と言われます。適切な例外ハンドラが見つからない場合、プログラムは終了し、例外に関する情報がコンソールに出力されます。

Javaの例外処理フレームワークは、実行時エラーの処理に使用されます。コンパイル時のエラーは、コードを記述する開発者によって修正する必要があります。そうしないとプログラムは実行されません。

Javaの例外処理キーワード

Javaは例外処理のための特定のキーワードを提供します。

  1. throw – エラーが発生した場合、例外オブジェクトが作成され、その後Javaランタイムが処理を開始してそれらを処理します。時には、コード内で明示的に例外を生成したいことがあります。例えば、ユーザー認証プログラムでは、パスワードがnullである場合、クライアントに例外を投げる必要があります。 throwキーワードは、例外をランタイムに投げて処理させるために使用されます。
  2. throws – メソッドで例外をスローし、それを処理しない場合、メソッドシグネチャにthrowsキーワードを使用して、呼び出し側のプログラムにメソッドでスローされる可能性のある例外を知らせます。呼び出し側のメソッドは、これらの例外を処理するか、throwsキーワードを使用してその呼び出し元のメソッドに伝播させることができます。 throws節に複数の例外を指定でき、main()メソッドでも使用できます。
  3. try-catch – コード内で例外処理にtry-catchブロックを使用します。 tryはブロックの開始であり、catchは例外を処理するためにtryブロックの末尾にあります。 tryブロックには複数のcatchブロックを持つことができます。 try-catchブロックはネストすることもできます。 catchブロックには、Exception型である必要があるパラメータが必要です。
  4. 最後にfinally ブロックはオプションであり、try-catch ブロックとのみ使用できます。例外が実行プロセスを停止させるため、いくつかのリソースが閉じられない可能性がありますが、finally ブロックを使用できます。 finally ブロックは常に実行されます。例外が発生したかどうかにかかわらず。

例外処理の例

ExceptionHandling.java
package com.journaldev.exceptions;

import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionHandling {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		try {
			testException(-5);
			testException(-10);
		} catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("Releasing resources");
		}
		testException(15);
	}

	public static void testException(int i) throws FileNotFoundException, IOException {
		if (i < 0) {
			FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
			throw myException;
		} else if (i > 10) {
			throw new IOException("Only supported for index 0 to 10");
		}
	}
}
  • testException() メソッドは、throw キーワードを使用して例外をスローしています。メソッドのシグネチャでは、throws キーワードを使用して呼び出し元にスローする可能性のある例外の種類を知らせます。
  • main() メソッドでは、main() メソッド内の try-catch ブロックを使用して例外を処理しています。処理しない場合は、main() メソッド内の throws 句でランタイムに伝播させます。
  • testException(-10) は例外のために実行されませんし、その後 finally ブロックが実行されます。

printStackTrace() は、デバッグ目的で Exception クラスにある便利なメソッドの1つです。

このコードは以下を出力します:

Output
java.io.FileNotFoundException: Negative Integer -5 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10) Releasing resources Exception in thread "main" java.io.IOException: Only supported for index 0 to 10 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)

注意すべき重要なポイント:

  • try 文なしで catch 句や finally 句を持つことはできません。
  • A try statement should have either catch block or finally block, it can have both blocks.
  • try-catch-finally ブロックの間にはコードを書くことはできません。
  • 1 つの try ステートメントで複数の catch ブロックを持つことができます。
  • try-catch ブロックは、if-else 文と同様にネストすることができます。
  • 1 つの try-catch ステートメントには 1 つの finally ブロックしか持てません。

Java 例外の階層構造

先に述べたように、例外が発生すると例外オブジェクトが作成されます。Java 例外は階層的であり、継承が使用されて異なるタイプの例外を分類します。Throwable は Java 例外の階層構造の親クラスであり、2 つの子オブジェクトがあります – ErrorExceptionException は、チェックされた Exception とランタイム Exception にさらに分かれています。

  1. エラー: Error は、アプリケーションの範囲外の例外的なシナリオであり、それらを予測して回復することは不可能です。例えば、ハードウェアの障害、Java 仮想マシン(JVM)のクラッシュ、またはメモリ不足のエラーです。これらの状況を処理しようとしないように、Error の別の階層構造があります。一部の一般的な Error には、OutOfMemoryErrorStackOverflowError があります。
  2. チェックされた例外: チェックされた例外は、プログラムで予期できる例外的なシナリオであり、それに対処しようとします。例えば、FileNotFoundExceptionです。この例外をキャッチし、ユーザーに有用なメッセージを提供し、デバッグ目的で適切にログに記録する必要があります。Exceptionは、すべてのチェックされたExceptionの親クラスです。チェックされたExceptionをスローしている場合は、同じメソッド内でそれをcatchするか、throwsキーワードを使用して呼び出し元に伝播する必要があります。
  3. ランタイム例外: ランタイム例外は、悪いプログラミングによって引き起こされます。例えば、配列から要素を取得しようとすることです。要素を取得する前に配列の長さを確認する必要があります。そうしないと、ランタイムでArrayIndexOutOfBoundExceptionがスローされる可能性があります。RuntimeExceptionは、すべてのランタイムExceptionの親クラスです。メソッドでランタイムExceptionthrowしている場合、メソッドのシグネチャthrows節でそれらを指定する必要はありません。より良いプログラミングでランタイム例外を回避することができます。

例外クラスのいくつかの有用なメソッド

Java Exceptionおよびそのすべてのサブクラスには特定のメソッドが提供されておらず、すべてのメソッドが基本クラスであるThrowableで定義されています。Exceptionクラスは、異なる種類のExceptionシナリオを指定するために作成されており、そのタイプに応じてExceptionを容易に識別し、処理できるようにします。Throwableクラスは、相互運用性のためにSerializableインターフェースを実装しています。

Throwableクラスのいくつかの有用なメソッドは次のとおりです:

  1. public String getMessage() – このメソッドは、ThrowableのメッセージStringを返し、例外を作成する際にメッセージを提供できます。
  2. public String getLocalizedMessage() – このメソッドは、サブクラスがロケール固有のメッセージを呼び出しプログラムに提供するためにオーバーライドできるように提供されています。このメソッドのThrowableクラスの実装は、例外メッセージを返すためにgetMessage()メソッドを使用します。
  3. public synchronized Throwable getCause() – このメソッドは、例外の原因を返します。原因が不明な場合はnullを返します。
  4. public String toString() – このメソッドは、String形式でThrowableに関する情報を返します。返されるStringには、Throwableクラスの名前とローカライズされたメッセージが含まれます。
  5. public void printStackTrace()–このメソッドはスタックトレース情報を標準エラーストリームに出力します。このメソッドはオーバーロードされており、スタックトレース情報をファイルやストリームに書き込むためにPrintStreamまたはPrintWriterを引数として渡すことができます。

Java 7の自動リソース管理およびキャッチブロックの改善

単一のtryブロックで多くの例外をcatchする場合、catchブロックのコードはエラーをログに記録するための冗長なコードで構成されることが多いことに気付くでしょう。Java 7では、改善されたcatchブロックの機能の1つが、単一のcatchブロックで複数の例外をキャッチすることができるようになったことです。この機能を使用したcatchブロックの例を次に示します:

catch (IOException | SQLException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

例外オブジェクトがfinalであり、catchブロック内で修正できないなど、いくつかの制約があります。詳細な解析はJava 7 Catch Block Improvementsで読むことができます。

ほとんどの場合、finally ブロックはリソースを閉じるために使用されます。時々、それらを閉じるのを忘れて、リソースが枯渇するとランタイム例外が発生します。これらの例外はデバッグが難しく、そのリソースを使用しているすべての場所を調べて、それが閉じられていることを確認する必要があります。Java 7では、その改善の1つとしてtry-with-resourcesがあり、この改善を使用するtry-catchブロック内でリソースを作成し、それを使用できます。実行がtry-catchブロックから出ると、実行環境はこれらのリソースを自動的に閉じます。次に、この改善を使用したtry-catchブロックの例を示します。

try (MyResource mr = new MyResource()) {
	System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
	e.printStackTrace();
}

A Custom Exception Class Example

Javaは私たちが使用するための多くの例外クラスを提供していますが、時には独自のカスタム例外クラスを作成する必要があるかもしれません。たとえば、特定の種類の例外について呼び出し元に適切なメッセージで通知するためです。エラーコードなどをトラッキングするためのカスタムフィールドを持つことができます。たとえば、テキストファイルのみを処理するメソッドを作成するとします。他の種類のファイルが入力として送信された場合には、適切なエラーコードを呼び出し元に提供できます。

まず、MyExceptionを作成します。

MyException.java
package com.journaldev.exceptions;

public class MyException extends Exception {

	private static final long serialVersionUID = 4664456874499611218L;

	private String errorCode = "Unknown_Exception";

	public MyException(String message, String errorCode) {
		super(message);
		this.errorCode=errorCode;
	}

	public String getErrorCode() {
		return this.errorCode;
	}
}

次に、CustomExceptionExampleを作成します。

CustomExceptionExample.java
package com.journaldev.exceptions;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class CustomExceptionExample {

	public static void main(String[] args) throws MyException {
		try {
			processFile("file.txt");
		} catch (MyException e) {
			processErrorCodes(e);
		}
	}

	private static void processErrorCodes(MyException e) throws MyException {
		switch (e.getErrorCode()) {
			case "BAD_FILE_TYPE":
				System.out.println("Bad File Type, notify user");
				throw e;
			case "FILE_NOT_FOUND_EXCEPTION":
				System.out.println("File Not Found, notify user");
				throw e;
			case "FILE_CLOSE_EXCEPTION":
				System.out.println("File Close failed, just log it.");
				break;
			default:
				System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
				e.printStackTrace();
		}
	}

	private static void processFile(String file) throws MyException {
		InputStream fis = null;

		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
		} finally {
			try {
				if (fis != null) fis.close();
			} catch (IOException e) {
				throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
			}
		}
	}
}

さまざまなメソッドから得たさまざまなエラーコードを処理するための別個のメソッドを持つことができます。そのうちのいくつかは消費されますが、それをユーザーに通知したくないかもしれません。それとは別に、問題をユーザーに通知するためにエラーコードをスローバックすることもあります。

ここでは、Exceptionを拡張しています。したがって、この例外が発生した場合は、メソッド内で処理するか、呼び出し元プログラムに返す必要があります。 RuntimeExceptionを拡張する場合、throws句でそれを指定する必要はありません。

これは設計上の決定でした。チェック済みのExceptionを使用すると、開発者が期待される例外を理解し、適切なアクションを取るのに役立ちます。

Javaでの例外処理のベストプラクティス

  • 特定の例外を使用する – 例外階層の基本クラスには有用な情報が提供されていないため、JavaにはIOExceptionなど、多くの例外クラスがあります。さらにFileNotFoundExceptionEOFExceptionなどのサブクラスがあります。常に特定の例外クラスをthrowおよびcatchするようにすると、呼び出し元が例外の根本原因を簡単に把握して処理できるようになります。これによりデバッグが容易になり、クライアントアプリケーションが適切に例外を処理できるようになります。
  • 早期の例外スローまたはFail-Fast – できるだけ早く例外をスローするようにしましょう。上記のprocessFile()メソッドを考えてみましょう。このメソッドにnull引数を渡すと、次のような例外が発生します:
Output
Exception in thread "main" java.lang.NullPointerException at java.io.FileInputStream.<init>(FileInputStream.java:134) at java.io.FileInputStream.<init>(FileInputStream.java:97) at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

デバッグ中には、例外の実際の発生場所を特定するためにスタックトレースを注意深く確認する必要があります。次のように実装ロジックを変更して、これらの例外を早めにチェックするようにします:

private static void processFile(String file) throws MyException {
	if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");

	// ... further processing
}

その後、例外のスタックトレースは、例外が発生した場所を明確に示します:

Output
com.journaldev.exceptions.MyException: File name can't be null at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
  • Catch Late – Javaはチェックされた例外を処理するか、メソッドシグネチャで宣言することを強制するため、開発者は時々例外をキャッチしてエラーをログに記録しようとします。しかし、この方法は害を及ぼします。なぜなら、呼び出し元のプログラムは例外に対する通知を受け取らないからです。例外を適切に処理できる場合にのみ例外をキャッチすべきです。たとえば、上記のメソッドでは、例外を呼び出し元のメソッドに戻して処理させています。同じメソッドは、例外を異なる方法で処理したい他のアプリケーションによって使用される可能性があります。どんな機能を実装する場合も、常に例外を呼び出し元に戻して、それらをどのように処理するかを決定させるべきです。
  • Closing Resources – 例外はプログラムの処理を停止させるため、すべてのリソースをfinallyブロックで閉じるか、Java 7のtry-with-resources拡張機能を使用して、Javaランタイムに閉じさせる必要があります。
  • 例外の記録 – 常に例外メッセージを記録し、例外をthrowする際には呼び出し元が例外が発生した理由を簡単に知ることができるように明確なメッセージを提供します。ただし例外を消費するだけで意味のある詳細を提供しない空のcatchブロックは常に避けるべきです。
  • 複数の例外に対する単一のcatchブロック – ほとんどの場合、例外の詳細を記録し、ユーザーにメッセージを提供します。この場合、Java 7の機能を使用して単一のcatchブロックで複数の例外を処理するべきです。このアプローチはコードサイズを削減し、見栄えも向上します。
  • カスタム例外の使用 – 設計時に例外処理戦略を定義し、複数の例外をthrowおよびcatchする代わりに、エラーコードを持つカスタム例外を作成し、呼び出し元のプログラムがこれらのエラーコードを処理できるようにします。異なるエラーコードを処理するためのユーティリティメソッドを作成し、使用するのも良いアイディアです。
  • 命名規則とパッケージング – カスタム例外を作成する際には、それが例外クラスであることが名前自体から明確にわかるようにExceptionで終わるようにしてください。また、それらをJava Development Kit(JDK)で行われているようにパッケージ化するようにも注意してください。例えば、IOExceptionはすべてのIO操作の基本例外です。
  • 例外を賢明に使用する – 例外はコストがかかり、時にはまったく例外をスローする必要がないことがあります。操作が成功したかどうかを呼び出し元のプログラムに示すためにブール変数を返すことができます。これは操作がオプションであり、失敗した場合にプログラムが停止しないようにしたい場合に役立ちます。たとえば、サードパーティのWebサービスからデータベースの株価を更新する際、接続が失敗した場合には例外をスローするのを避けたいかもしれません。
  • スローされる例外を文書化する – メソッドがスローする例外を明確に指定するためにJavadocの@throwsを使用します。他のアプリケーションが使用するためのインターフェースを提供している場合に非常に役立ちます。

結論

この記事では、Javaでの例外処理について学びました。throwおよびthrowsについて学びました。また、try(およびtry-with-resources)、catch、およびfinallyブロックについても学びました。

Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java