導入
例外は、プログラムの実行中に発生し、通常のフローを妨げるエラーイベントです。Javaは、Java例外処理として知られる例外シナリオを処理するための堅牢でオブジェクト指向の方法を提供します。
Javaの例外は、ユーザーが入力した間違ったデータ、ハードウェアの障害、ネットワーク接続の障害、またはダウンしているデータベースサーバーなど、さまざまな状況から発生する可能性があります。特定の例外シナリオで何をするかを指定するコードを例外処理と呼びます。
例外のスローとキャッチ
Javaは、ステートメントの実行中にエラーが発生すると、例外オブジェクトを作成します。例外オブジェクトには、メソッド階層、例外が発生した行番号、および例外のタイプなど、多くのデバッグ情報が含まれています。
メソッドで例外が発生した場合、例外オブジェクトを作成し、ランタイム環境に渡すプロセスは「例外のスロー」と呼ばれます。プログラムの通常のフローが停止し、Javaランタイム環境(JRE)は例外のハンドラを検索しようとします。例外ハンドラは、例外オブジェクトを処理できるコードブロックです。
- 例外ハンドラを見つけるためのロジックは、エラーが発生したメソッドでの検索から開始されます。
- 適切なハンドラが見つからない場合、呼び出し元のメソッドに移動します。
- そして、その繰り返しです。
したがって、メソッドの呼び出しスタックがA->B->Cであり、メソッドCで例外が発生した場合、適切なハンドラの検索はC->B->Aに移動します。
適切な例外ハンドラが見つかると、例外オブジェクトはハンドラに渡され、処理されます。ハンドラは「例外をキャッチする」と言われます。適切な例外ハンドラが見つからない場合、プログラムは終了し、例外に関する情報がコンソールに出力されます。
Javaの例外処理フレームワークは、実行時エラーの処理に使用されます。コンパイル時のエラーは、コードを記述する開発者によって修正する必要があります。そうしないとプログラムは実行されません。
Javaの例外処理キーワード
Javaは例外処理のための特定のキーワードを提供します。
- throw – エラーが発生した場合、例外オブジェクトが作成され、その後Javaランタイムが処理を開始してそれらを処理します。時には、コード内で明示的に例外を生成したいことがあります。例えば、ユーザー認証プログラムでは、パスワードが
null
である場合、クライアントに例外を投げる必要があります。throw
キーワードは、例外をランタイムに投げて処理させるために使用されます。 - throws – メソッドで例外をスローし、それを処理しない場合、メソッドシグネチャに
throws
キーワードを使用して、呼び出し側のプログラムにメソッドでスローされる可能性のある例外を知らせます。呼び出し側のメソッドは、これらの例外を処理するか、throws
キーワードを使用してその呼び出し元のメソッドに伝播させることができます。throws
節に複数の例外を指定でき、main()
メソッドでも使用できます。 - try-catch – コード内で例外処理に
try-catch
ブロックを使用します。try
はブロックの開始であり、catch
は例外を処理するためにtry
ブロックの末尾にあります。try
ブロックには複数のcatch
ブロックを持つことができます。try-catch
ブロックはネストすることもできます。catch
ブロックには、Exception
型である必要があるパラメータが必要です。 - 最後に –
finally
ブロックはオプションであり、try-catch
ブロックとのみ使用できます。例外が実行プロセスを停止させるため、いくつかのリソースが閉じられない可能性がありますが、finally
ブロックを使用できます。finally
ブロックは常に実行されます。例外が発生したかどうかにかかわらず。
例外処理の例
testException()
メソッドは、throw
キーワードを使用して例外をスローしています。メソッドのシグネチャでは、throws
キーワードを使用して呼び出し元にスローする可能性のある例外の種類を知らせます。main()
メソッドでは、main()
メソッド内のtry-catch
ブロックを使用して例外を処理しています。処理しない場合は、main()
メソッド内のthrows
句でランタイムに伝播させます。testException(-10)
は例外のために実行されませんし、その後finally
ブロックが実行されます。
printStackTrace()
は、デバッグ目的で Exception
クラスにある便利なメソッドの1つです。
このコードは以下を出力します:
Outputjava.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 eithercatch
block orfinally
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 つの子オブジェクトがあります – Error
と Exception
。 Exception
は、チェックされた Exception
とランタイム Exception
にさらに分かれています。
- エラー:
Error
は、アプリケーションの範囲外の例外的なシナリオであり、それらを予測して回復することは不可能です。例えば、ハードウェアの障害、Java 仮想マシン(JVM)のクラッシュ、またはメモリ不足のエラーです。これらの状況を処理しようとしないように、Error
の別の階層構造があります。一部の一般的なError
には、OutOfMemoryError
とStackOverflowError
があります。 - チェックされた例外: チェックされた
例外
は、プログラムで予期できる例外的なシナリオであり、それに対処しようとします。例えば、FileNotFoundException
です。この例外をキャッチし、ユーザーに有用なメッセージを提供し、デバッグ目的で適切にログに記録する必要があります。Exception
は、すべてのチェックされたException
の親クラスです。チェックされたException
をスローしている場合は、同じメソッド内でそれをcatch
するか、throws
キーワードを使用して呼び出し元に伝播する必要があります。 - ランタイム例外: ランタイム
例外
は、悪いプログラミングによって引き起こされます。例えば、配列から要素を取得しようとすることです。要素を取得する前に配列の長さを確認する必要があります。そうしないと、ランタイムでArrayIndexOutOfBoundException
がスローされる可能性があります。RuntimeException
は、すべてのランタイムException
の親クラスです。メソッドでランタイムException
をthrow
している場合、メソッドのシグネチャthrows
節でそれらを指定する必要はありません。より良いプログラミングでランタイム例外を回避することができます。
例外クラスのいくつかの有用なメソッド
Java Exception
およびそのすべてのサブクラスには特定のメソッドが提供されておらず、すべてのメソッドが基本クラスであるThrowable
で定義されています。Exception
クラスは、異なる種類のException
シナリオを指定するために作成されており、そのタイプに応じてException
を容易に識別し、処理できるようにします。Throwable
クラスは、相互運用性のためにSerializable
インターフェースを実装しています。
Throwable
クラスのいくつかの有用なメソッドは次のとおりです:
- public String getMessage() – このメソッドは、
Throwable
のメッセージString
を返し、例外を作成する際にメッセージを提供できます。 - public String getLocalizedMessage() – このメソッドは、サブクラスがロケール固有のメッセージを呼び出しプログラムに提供するためにオーバーライドできるように提供されています。このメソッドの
Throwable
クラスの実装は、例外メッセージを返すためにgetMessage()
メソッドを使用します。 - public synchronized Throwable getCause() – このメソッドは、例外の原因を返します。原因が不明な場合は
null
を返します。 - public String toString() – このメソッドは、
String
形式でThrowable
に関する情報を返します。返されるString
には、Throwable
クラスの名前とローカライズされたメッセージが含まれます。 - public void printStackTrace()–このメソッドはスタックトレース情報を標準エラーストリームに出力します。このメソッドはオーバーロードされており、スタックトレース情報をファイルやストリームに書き込むために
PrintStream
またはPrintWriter
を引数として渡すことができます。
Java 7の自動リソース管理およびキャッチブロックの改善
単一のtry
ブロックで多くの例外をcatch
する場合、catch
ブロックのコードはエラーをログに記録するための冗長なコードで構成されることが多いことに気付くでしょう。Java 7では、改善されたcatch
ブロックの機能の1つが、単一のcatch
ブロックで複数の例外をキャッチすることができるようになったことです。この機能を使用したcatch
ブロックの例を次に示します:
例外オブジェクトがfinalであり、catch
ブロック内で修正できないなど、いくつかの制約があります。詳細な解析はJava 7 Catch Block Improvementsで読むことができます。
ほとんどの場合、finally
ブロックはリソースを閉じるために使用されます。時々、それらを閉じるのを忘れて、リソースが枯渇するとランタイム例外が発生します。これらの例外はデバッグが難しく、そのリソースを使用しているすべての場所を調べて、それが閉じられていることを確認する必要があります。Java 7では、その改善の1つとしてtry-with-resources
があり、この改善を使用するtry-catch
ブロック内でリソースを作成し、それを使用できます。実行がtry-catch
ブロックから出ると、実行環境はこれらのリソースを自動的に閉じます。次に、この改善を使用したtry-catch
ブロックの例を示します。
A Custom Exception Class Example
Javaは私たちが使用するための多くの例外クラスを提供していますが、時には独自のカスタム例外クラスを作成する必要があるかもしれません。たとえば、特定の種類の例外について呼び出し元に適切なメッセージで通知するためです。エラーコードなどをトラッキングするためのカスタムフィールドを持つことができます。たとえば、テキストファイルのみを処理するメソッドを作成するとします。他の種類のファイルが入力として送信された場合には、適切なエラーコードを呼び出し元に提供できます。
まず、MyException
を作成します。
次に、CustomExceptionExample
を作成します。
さまざまなメソッドから得たさまざまなエラーコードを処理するための別個のメソッドを持つことができます。そのうちのいくつかは消費されますが、それをユーザーに通知したくないかもしれません。それとは別に、問題をユーザーに通知するためにエラーコードをスローバックすることもあります。
ここでは、Exception
を拡張しています。したがって、この例外が発生した場合は、メソッド内で処理するか、呼び出し元プログラムに返す必要があります。 RuntimeException
を拡張する場合、throws
句でそれを指定する必要はありません。
これは設計上の決定でした。チェック済みのException
を使用すると、開発者が期待される例外を理解し、適切なアクションを取るのに役立ちます。
Javaでの例外処理のベストプラクティス
- 特定の例外を使用する – 例外階層の基本クラスには有用な情報が提供されていないため、Javaには
IOException
など、多くの例外クラスがあります。さらにFileNotFoundException
、EOFException
などのサブクラスがあります。常に特定の例外クラスをthrow
およびcatch
するようにすると、呼び出し元が例外の根本原因を簡単に把握して処理できるようになります。これによりデバッグが容易になり、クライアントアプリケーションが適切に例外を処理できるようになります。 - 早期の例外スローまたはFail-Fast – できるだけ早く例外をスローするようにしましょう。上記の
processFile()
メソッドを考えてみましょう。このメソッドにnull
引数を渡すと、次のような例外が発生します:
OutputException 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)
デバッグ中には、例外の実際の発生場所を特定するためにスタックトレースを注意深く確認する必要があります。次のように実装ロジックを変更して、これらの例外を早めにチェックするようにします:
その後、例外のスタックトレースは、例外が発生した場所を明確に示します:
Outputcom.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