Javaの例外、パート1:例外処理の基本

Javaの例外は、プログラムの失敗を表現して処理するために使用されるライブラリタイプと言語機能です。ソースコードで障害がどのように表されるかを理解したい場合は、適切な場所に来ました。Java例外の概要に加えて、オブジェクトのスロー、失敗する可能性のあるコードの試行、スローされたオブジェクトのキャッチ、および例外がスローされた後のJavaコードのクリーンアップに関するJavaの言語機能の概要を説明します。

このチュートリアルの前半では、Java1.0以降に使用されてきた基本的な言語機能とライブラリタイプについて学習します。後半では、最近のJavaバージョンで導入された高度な機能について説明します。

このチュートリアルのコード例はJDK12と互換性があることに注意してください。

ダウンロードコードを取得するこのチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

Javaの例外とは何ですか?

失敗は、Javaプログラムの通常の動作が予期しない動作によって中断された場合に発生します。この相違は例外として知られています。たとえば、プログラムがファイルを開いてその内容を読み取ろうとしましたが、ファイルが存在しません。Javaは例外をいくつかのタイプに分類するので、それぞれについて考えてみましょう。

チェックされた例外

Javaは、外部要因(ファイルの欠落など)から発生する例外チェック済み例外として分類します。Javaコンパイラは、そのような例外が発生した場所で処理(修正)されるか、他の場所で処理されるように文書化されているかを確認します。

例外ハンドラ

例外ハンドラは、例外を処理するコードのシーケンスです。コンテキストに問い合わせます(つまり、例外が発生したときにスコープ内にあった変数から保存された値を読み取ります)。次に、学習した内容を使用して、Javaプログラムを通常の動作のフローに復元します。たとえば、例外ハンドラーが保存されたファイル名を読み取り、不足しているファイルを置き換えるようにユーザーに要求する場合があります。

ランタイム(チェックされていない)例外

プログラムが整数を整数0で除算しようとするとします。この不可能性は、別の種類の例外、つまり実行時例外を示しています。チェックされた例外とは異なり、ランタイム例外は通常、不十分に記述されたソースコードから発生するため、プログラマーが修正する必要があります。コンパイラは実行時例外を処理または他の場所で処理されるように文書化されていることを確認していないので、あなたは、ランタイム例外と考えることができますチェックされない例外

ランタイム例外について

ランタイム例外を処理するようにプログラムを変更することもできますが、ソースコードを修正することをお勧めします。ランタイム例外は、ライブラリのメソッドに無効な引数を渡すことで発生することがよくあります。バグのある呼び出しコードを修正する必要があります。

エラー

一部の例外は、プログラムの実行を継続する能力を危険にさらすため、非常に深刻です。たとえば、プログラムがJVMからメモリを割り当てようとしましたが、要求を満たすのに十分な空きメモリがありません。プログラムClass.forName()がメソッド呼び出しを介してクラスファイルを読み込もうとしたが、クラスファイルが破損している場合、別の深刻な状況が発生します。この種の例外はエラーとして知られています。JVMがエラーから回復できない可能性があるため、エラーを自分で処理しようとしないでください。

ソースコードの例外

例外は、ソースコードでエラーコードまたはオブジェクトとして表される場合があります。両方を紹介し、オブジェクトが優れている理由を示します。

エラーコードとオブジェクト

Cなどのプログラミング言語は、整数ベースのエラーコードを使用して、失敗と失敗の理由(つまり、例外)を表します。次にいくつかの例を示します。

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

Cのchdir()(ディレクトリの変更)関数は整数を返します:成功した場合は0、失敗した場合は-1。同様に、Cのfopen()(ファイルを開く)関数は、成功した場合は構造体への非nullポインター(整数アドレス)を返し、失敗FILEした場合はnull(0)ポインター(定数で表される)を返しますNULL。いずれの場合も、失敗の原因となった例外を特定するには、グローバルerrno変数の整数ベースのエラーコードを読み取る必要があります。

エラーコードにはいくつかの問題があります。

  • 整数は無意味です。それらが表す例外については説明していません。たとえば、6はどういう意味ですか?
  • コンテキストをエラーコードに関連付けるのは厄介です。たとえば、開くことができなかったファイルの名前を出力したいが、ファイルの名前をどこに保存するのでしょうか。
  • 整数は任意であるため、ソースコードを読み取るときに混乱が生じる可能性があります。たとえば、失敗をテストする代わりにif (!chdir("C:\\temp"))!NOTを意味する)を指定する方if (chdir("C:\\temp"))が明確です。ただし、成功を示すために0が選択if (chdir("C:\\temp"))されたため、失敗をテストするには0を指定する必要があります。
  • エラーコードは無視しやすいため、バグのあるコードにつながる可能性があります。たとえば、プログラマーchdir("C:\\temp");if (fp == NULL)チェックを指定して無視することができます。さらに、プログラマーはを調べる必要はありませんerrno。失敗をテストしないことにより、いずれかの関数が失敗インジケーターを返すと、プログラムは不規則に動作します。

これらの問題を解決するために、Javaは例外処理への新しいアプローチを採用しました。Javaでは、例外を記述するオブジェクトを、これらのオブジェクトのスローとキャッチに基づくメカニズムと組み合わせます。オブジェクトとエラーコードを使用して例外を示すことの利点は次のとおりです。

  • オブジェクトは、意味のある名前のクラスから作成できます。たとえば、FileNotFoundExceptionjava.ioパッケージ内の)は6よりも意味があります。
  • オブジェクトは、さまざまなフィールドにコンテキストを格納できます。たとえば、メッセージ、開くことができなかったファイルの名前、解析操作が失敗した最新の位置、その他の項目をオブジェクトのフィールドに保存できます。
  • if失敗をテストするためにステートメントを使用することはありません。代わりに、例外オブジェクトは、プログラムコードとは別のハンドラーにスローされます。その結果、ソースコードが読みやすくなり、バグが発生する可能性が低くなります。

Throwableとそのサブクラス

Javaは、さまざまな種類の例外を表すクラスの階層を提供します。これらのクラスはに根ざしているjava.langパッケージのThrowableそのとともに、クラスExceptionRuntimeExceptionおよびErrorサブクラス。

Throwable例外が関係する究極のスーパークラスです。Throwableスロー(およびその後キャッチ)できるのは、そのサブクラスから作成されたオブジェクトのみです。このようなオブジェクトは、スローアブルとして知られています。

Throwableオブジェクトが関連付けられている詳細メッセージ例外が記載されています。Throwable詳細メッセージの有無にかかわらずオブジェクトを作成するために、以下で説明するペアを含むいくつかのコンストラクターが提供されています。

  • Throwable()は、Throwable詳細メッセージなしでを作成します。このコンストラクターは、コンテキストがない状況に適しています。たとえば、スタックが空または満杯であることだけを知りたいとします。
  • Throwable(String message)は、詳細メッセージとしてThrowablewithmessageを作成します。このメッセージは、ユーザーに出力したり、ログに記録したりできます。

ThrowableString getMessage()詳細メッセージを返すメソッドを提供します。また、後で紹介する追加の便利なメソッドも提供します。

Exceptionクラス

Throwable2つの直接サブクラスがあります。これらのサブクラスの1つはですException。これは、外部要因(存在しないファイルからの読み取りの試行など)から発生する例外を記述します。Exceptionと同じコンストラクター(同一のパラメーターリストを持つ)を宣言し、Throwable各コンストラクターはThrowable対応するコンストラクターを呼び出します。のメソッドをException継承しThrowableます。新しいメソッドは宣言されていません。

Javaには、直接サブクラス化する多くの例外クラスが用意されていますException。次に3つの例を示します。

  • CloneNotSupportedExceptionは、クラスがCloneableインターフェイスを実装していないオブジェクトのクローンを作成しようとしたことを示します。どちらのタイプもjava.langパッケージに含まれています。
  • IOExceptionは、ある種のI / O障害が発生したことを示します。このタイプはjava.ioパッケージに含まれています。
  • ParseExceptionは、テキストの解析中に障害が発生したことを示します。このタイプはjava.textパッケージに含まれています。

Exceptionサブクラス名が単語で終わっていることに注意してくださいException。この規則により、クラスの目的を簡単に識別できます。

通常Exception、独自の例外クラス(名前はで終わる必要があります)を使用してサブクラス(またはそのサブクラスの1つ)を作成しますException。以下に、カスタムサブクラスの例をいくつか示します。

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

最初の例は、詳細メッセージを必要としない例外クラスについて説明しています。これはException()、デフォルトのnoargumentコンストラクターが呼び出す、を呼び出すThrowable()

The second example describes an exception class whose constructor requires a detail message and the name of the empty directory. The constructor invokes Exception(String message), which invokes Throwable(String message).

Objects instantiated from Exception or one of its subclasses (except for RuntimeException or one of its subclasses) are checked exceptions.

The RuntimeException class

Exception is directly subclassed by RuntimeException, which describes an exception most likely arising from poorly written code. RuntimeException declares the same constructors (with identical parameter lists) as Exception, and each constructor invokes its Exception counterpart. RuntimeException inherits Throwable's methods. It declares no new methods.

Java provides many exception classes that directly subclass RuntimeException. The following examples are all members of the java.lang package:

  • ArithmeticException signals an illegal arithmetic operation, such as attempting to divide an integer by 0.
  • IllegalArgumentException signals that an illegal or inappropriate argument has been passed to a method.
  • NullPointerException signals an attempt to invoke a method or access an instance field via the null reference.

Objects instantiated from RuntimeException or one of its subclasses are unchecked exceptions.

The Error class

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.