第11章 例外

目次

11.1 例外の種類と原因
11.1 例外の種類
11.1 例外の原因
11.1 非同期例外
11.2 例外のコンパイル時チェック
11.2 式の例外分析
11.2 文およびコンストラクタ呼出しの例外分析
11.2 例外チェック
11.3 例外の実行時処理

プログラムがJavaプログラミング言語のセマンティック制約に違反すると、Java Virtual Machineは、このエラーを例外としてプログラムに通知します。

このような違反の例として、配列の範囲外への索引付けが試みられます。 一部のプログラミング言語とその実装は、プログラムを意図的に終了することによってそのようなエラーに反応します。他のプログラミング言語では、実装が任意の方法または予測不可能な方法で反応できます。 これらのどちらのアプローチも、Java SE Platformの設計目標と互換性がありません。移植性と堅牢性を提供します。

かわりに、Javaプログラミング言語では、セマンティック制約に違反したときに例外がスローされ、例外が発生した時点からプログラマが指定できる時点まで、非ローカルで制御が転送されるように指定します。

例外が発生した時点からスローされ、コントロールが転送された時点でキャッチされていると言われています。

プログラムは、throw文(§14.18)を使用して明示的に例外をスローすることもできます。

throw文を明示的に使用すると、通常負の値は想定されない整数値-1などの面白い値を返すことによって、エラー条件を処理する旧式の方法の代替手段が提供されます。 経験によると、このような面白い値が呼び出し元によって無視されたりチェックされなかったりすることが多すぎるため、堅牢でないプログラムや望ましくない動作、あるいはその両方が発生することになります。

すべての例外は、クラスThrowableまたはそのサブクラスの1つ(§11.1)のインスタンスによって表されます。 このようなオブジェクトは、例外が発生した時点からそれを捕捉するハンドラに情報を伝達するために使用できます。 ハンドラは、try文(§14.20)のcatch句によって確立されます。

例外をスローするプロセス中、Java Virtual Machineは、現在のスレッドで実行が開始されたが完了していない式、文、メソッドおよびコンストラクタの呼出し、イニシャライザおよびフィールド初期化式を1つずつ突然完了します。 このプロセスは、例外のクラスまたは例外のクラスのスーパークラス(§11.2)に名前を付けて、その特定の例外を処理することを示すハンドラが見つかるまで続きます。 そのようなハンドラが見つからなかった場合、例外は捕捉されない例外ハンドラ(§11.3)の階層の1つによって処理され、例外が処理されないようにあらゆる努力がなされます。

Java SE Platformの例外メカニズムは、同期モデル(§17.1)と統合されているため、モニターはsynchronized文(§14.19)としてロック解除され、synchronizedメソッド(§8.4.3.6§15.12)の呼出しが突然完了します。

11.1. 例外の種類と原因

11.1.1. 例外の種類

例外は、クラスThrowableのインスタンス(Objectの直接サブクラス)またはそのサブクラスの1つによって表されます。

Throwableとそのすべてのサブクラスは、まとめて例外クラスです。

クラスExceptionおよびErrorは、Throwableの直接サブクラスです。

  • Exceptionは、通常のプログラムがリカバリする必要があるすべての例外のスーパークラスです。

    クラスRuntimeExceptionは、Exceptionの直接サブクラスです。 RuntimeExceptionは、式評価中に多くの理由でスローされる可能性があるすべての例外のスーパークラスですが、そこからリカバリが可能な場合もあります。

    RuntimeExceptionおよびそのすべてのサブクラスは、まとめて実行時例外クラスです。

  • Errorは、通常のプログラムが通常リカバリを予期していないすべての例外のスーパークラスです。

    Errorとそのすべてのサブクラスは、まとめてエラー・クラスです。

未チェック例外クラスは、実行時例外クラスおよびエラー・クラスです。

チェックされた例外クラスは、チェックされていない例外クラス以外のすべての例外クラスです。 つまり、チェックされた例外クラスはThrowableであり、RuntimeExceptionとそのサブクラスおよびErrorとそのサブクラス以外のすべてのサブクラスです。

プログラムは、Java SE Platform APIの既存の例外クラスをthrow文で使用したり、必要に応じて、追加の例外クラスをThrowableのサブクラスまたは任意のサブクラスとして定義できます。 例外ハンドラ(§11.2)のコンパイル時チェックを利用するには、通常、ほとんどの新しい例外クラスをチェック例外クラスとして定義します。つまり、RuntimeExceptionのサブクラスではないExceptionのサブクラスとして定義します。

クラスErrorは、クラス階層のExceptionとは異なるThrowableの別のサブクラスであり、プログラムが慣用句「} catch (Exception e) {」(§11.2.3)を使用して、リカバリが通常不可能なエラーを捕捉せずにリカバリが可能なすべての例外をキャッチできるようにします。

Throwableのサブクラスを汎用にすることはできません(§8.1.2)。

11.1.2. 例外の原因

次の4つの理由のいずれかで例外がスローされます。

  • throw文(§14.18)が実行されました。

  • 有効なassert文が実行され、そのブール式の評価がfalse (§14.10)に評価されました。

  • 異常な実行条件がJava Virtual Machineによって同期的に検出されました。

    • 式の評価は、整数をゼロで除算するなど、Javaプログラミング言語(§15.6)の通常のセマンティクスに違反しています。

    • プログラムの一部のロード、リンクまたは初期化中にエラーが発生します(§12.2§12.3§12.4)。この場合、LinkageErrorのサブクラスのインスタンスがスローされます。

    • 内部エラーまたはリソース制限により、Java Virtual MachineはJavaプログラミング言語のセマンティクスを実装できなくなり、この場合、VirtualMachineErrorのサブクラスのインスタンスがスローされます。

    これらの例外は、プログラム内の任意のポイントでスローされるのではなく、式の評価または文の実行の考えられる結果として指定されたポイントでスローされます。

  • 非同期例外が発生しました(§11.1.3)。

11.1.3. 非同期例外

ほとんどの例外は、それらが発生したスレッドによるアクションの結果として同期的に発生し、プログラム内でそのような例外が発生する可能性があると指定された時点で発生します。 非同期例外とは対照的に、プログラムの実行のどの時点でも発生する可能性のある例外です。

非同期例外は、Java Virtual Machineの内部エラーまたはリソース制限の結果としてのみ発生し、Javaプログラミング言語のセマンティクスを実装できません。 スローされる非同期例外は、VirtualMachineErrorのサブクラスのインスタンスです。

VirtualMachineErrorのサブクラスであるStackOverflowErrorは、nativeメソッドの実行またはJava Virtual Machineリソースの制限により、メソッドの起動(§15.12.4.5)および非同期によって同期的にスローされる可能性があることに注意してください。 同様に、VirtualMachineErrorの別のサブクラスであるOutOfMemoryErrorは、クラス・インスタンスの作成時(§15.9.4§12.5)、配列の作成時(§15.10.2§10.6)、クラス初期化時(§12.4.2)、ボクシング変換時(§5.1.7)、および非同期で同期的にスローされます。

Java SEプラットフォームでは、非同期例外がスローされる前に、少量の実行が制限されます。

非同期例外はまれですが、高品質のマシン・コードを生成する場合は、そのセマンティクスの適切な理解が必要です。

前述の遅延は、最適化されたコードが、Javaプログラミング言語のセマンティクスに従い、これらの例外を処理することが実用的なポイントで検出およびスローすることを許可されています。 単純な実装では、各制御転送命令の時点で非同期例外をポーリングする場合があります。 プログラムは有限サイズであるため、非同期例外を検出する際の合計遅延にバインドされます。 制御転送間で非同期例外が発生しないため、コード・ジェネレータには、パフォーマンス向上のために制御転送間で計算を並べ替える柔軟性があります。 Marc Feeley著『Polling Efficiently on Stock Hardware』、Proc. 1993 Conference on Functional Programming and Computer Architecture、デンマーク、コペンハーゲン、pp. 179-187がさらに読むことをお勧めします。

11.2. コンパイル時の例外検査

Javaプログラミング言語では、プログラムに、メソッドまたはコンストラクタ(§8.4.6§8.8.5)の実行によって生じる可能性のあるチェックされた例外のハンドラが含まれている必要があります。 例外ハンドラが存在するかどうかのコンパイル時チェックは、適切に処理されない例外の数を減らすように設計されています。 考えられる結果である各チェック例外について、メソッドまたはコンストラクタのthrows句は、その例外のクラスまたはその例外のクラスのスーパークラスの1つ(§11.2.3)を記述する必要があります。

throws句で指定されたチェック済例外クラス(§11.1.1)は、メソッドまたはコンストラクタの実装者とユーザーとの間の契約の一部です。 オーバーライド・メソッドのthrows句では、このメソッドが、オーバーライドされたメソッドがthrows句によってスローされる(§8.4.8.3)ために許可されていないチェック例外をスローするように指定できません。 インタフェースが関係する場合、1つのオーバーライド宣言で複数のメソッド宣言をオーバーライドできます。 この場合、オーバーライドする宣言には、オーバーライドされるすべての宣言(§9.4.1)と互換性のあるthrows句が必要です。

チェックされていない例外クラス(§11.1.1)は、コンパイル時チェックから除外されます。

エラー・クラスは、プログラム内の多くのポイントで発生し、それらからのリカバリが困難または不可能であるため、免除されます。 このような例外を宣言するプログラムは、無意味に混乱してしまいます。 洗練されたプログラムは、これらの条件のいくつかを捕捉し、回復しようとします。

ランタイム例外クラスは、Javaプログラミング言語の設計者の判断において、このような例外を宣言する必要がプログラムの正確性の確立に大きく役立たないため、免除されます。 Javaプログラミング言語の操作および構成の多くは、実行時に例外が発生する可能性があります。 Javaコンパイラで使用可能な情報、およびコンパイラが実行する分析のレベルは、プログラマにとって明らかであっても、通常、このような実行時例外が発生しないことを確立するには十分ではありません。 このような例外クラスを宣言する必要は、プログラマにとって単なる刺激になります。

たとえば、あるコードでは、構築によってnull参照を含むことができない循環データ構造を実装する場合があります。プログラマは、NullPointerExceptionが発生できないことを確認できますが、Javaコンパイラがそれを証明するのは困難です。 データ構造のそのようなグローバルな特性を確立するために必要な定理的証明技術は、この仕様の範囲を超えている。

§11.2.1および§11.2.2のルールに従って文または式を実行すると、クラスEがスローされる場合、文または式は例外クラスEスローできるとします。

catch句では、その捕捉可能な例外クラスを捕捉できます。

  • uni-catch句の捕捉可能な例外クラスは、その例外パラメータの宣言された型です(§14.20)。

  • マルチcatch句の捕捉可能な例外クラスは、その例外パラメータのタイプを示すUNION内の代替クラスです。

11.2.1. 式の例外分析

クラス・インスタンス作成式(§15.9)は、次のいずれかの場合に例外クラスEをスローできます。

  • 式は修飾クラス・インスタンス作成式であり、修飾式はEをスローできます。または

  • 引数リストの一部の式は、Eをスローできます。

  • Eは、選択したコンストラクタ(§15.12.2.6)の呼出しタイプの例外タイプの1つです。または

  • クラス・インスタンス作成式にはClassBodyが含まれ、ClassBodyの一部のインスタンス・イニシャライザまたはインスタンス変数イニシャライザはEをスローできます。

メソッド呼出し式(§15.12)は、次のいずれかの場合に例外クラスEをスローできます。

  • メソッド呼出し式は、Primary . [TypeArguments] Identifierの形式であり、Primary式はEをスローできます。

  • 引数リストの一部の式は、Eをスローできます。

  • Eは、選択したメソッドの呼出しタイプ(§15.12.2.6)の例外タイプの1つです。

ラムダ式(§15.27)は例外クラスをスローできません。

switch式(§15.28)は、次のいずれかの場合に例外クラスEをスローできます。

  • セレクタ式では、Eをスローできます。または

  • switchルール式、switchルール・ブロック、switchルールthrow文またはswitchブロック内のswitchラベル付き文グループでは、Eをスローできます。

他のすべての種類の式について、その直前の副次式の1つがEをスローできる場合、式は例外クラスEをスローできます。

Primary :: [TypeArguments] Identifier形式のメソッド参照式(§15.13)は、Primaryサブ式で例外クラスをスローできる場合に、例外クラスをスローできることに注意してください。 対照的に、ラムダ式は何もスローできず、例外分析を実行する直後の副式もありません。 例外クラスをスローできる、式および文を含むラムダ式の本体です。

11.2.2. 文およびコンストラクタ呼出しの例外分析

throw文(§14.18)で、スローされた式に静的型Eがあり、finalまたはeffective final例外パラメータではない場合、スローされた式がスローできるEまたは例外クラスをスローできます。

たとえば、文throw new java.io.FileNotFoundException();java.io.FileNotFoundExceptionのみをスローできます。 正式には、java.io.FileNotFoundExceptionのサブクラスまたはスーパークラスを「スロー」できるわけではありません。

スローされた式がcatchCの最終的または事実上最終的な例外パラメータであるthrow文では、次の場合に例外クラスEをスローできます。

  • Eは、Cを宣言するtry文のtryブロックがスローできる例外クラスです。

  • Eに、Cの捕捉可能例外クラスのいずれかと代入の互換性がある。

  • Eは、同じtry文でCの左側に宣言されたcatch句の捕捉可能な例外クラスのいずれとも互換性がありません。

try文(§14.20)は、次のいずれかの場合に例外クラスEをスローできます。

  • tryブロックはEをスローするか、(try-with-resources文で)リソースの初期化に使用される式がEをスローするか、リソースのclose()メソッドの自動呼出し(try-with- で)をスローできます。resource文)はEをスローでき、Etry文の任意のcatch句の捕捉可能な例外クラスと互換性がなく、finallyブロックが存在しないか、finallyブロックが正常に完了できるかのいずれかです。

  • try文の一部のcatchブロックは、Eをスローでき、finallyブロックが存在しないか、finallyブロックが正常に完了できます。または、

  • finallyブロックが存在し、Eをスローできます。

switch文(§14.11)は、次のいずれかの場合に例外クラスEをスローできます。

  • セレクタ式では、Eをスローできます。または

  • switchルール式、switchルール・ブロック、switchルールthrow文またはswitchブロック内のswitchラベル付き文グループでは、Eをスローできます。

Sに即時に含まれる式または文がEをスローできる場合、その他の文Sは例外クラスEをスローできます。

コンストラクタ呼出し(§8.8.7.1)は、次のいずれかの場合に例外クラスEをスローできます。

  • コンストラクタ呼出しのパラメータ・リストの式のいくつかでは、Eをスローできます。または

  • Eは、呼び出されるコンストラクタのthrows句の例外クラスであると判断されます(§15.12.2.6)。

11.2.3. 例外チェック

Eがチェック例外クラスで、Eがメソッドまたはコンストラクタのthrows句で宣言されているクラスのサブクラスでない場合に、メソッドまたはコンストラクタ本体がいくつかの例外クラスEをスローできる場合、コンパイル時にエラーが発生します。

Eがチェック例外クラスで、Eがラムダ式によってターゲット指定された関数型のthrows句で宣言されているクラスのサブクラスでない場合に、ラムダ本体が一部の例外クラスEをスローできる場合、コンパイル時にエラーが発生します。

指定されたクラスまたはインタフェースのクラス変数イニシャライザ(§8.3.2)または静的イニシャライザ(§8.7)がチェックされた例外クラスをスローできる場合、コンパイル時にエラーが発生します。

名前付きクラスのインスタンス変数イニシャライザ(§8.3.2)またはインスタンス・イニシャライザ(§8.6)がチェック例外クラスをスローできる場合、コンパイル時にエラーが発生します。ただし、指定されたクラスに少なくとも1つの明示的に宣言されたコンストラクタがあり、例外クラスまたはそのスーパークラスのいずれかが、各コンストラクタのthrows句で明示的に宣言されている場合を除きます。

匿名クラスのインスタンス変数イニシャライザまたはインスタンス・イニシャライザ(§15.9.5)が例外クラスをスローできる場合、コンパイル時エラーは発生しないことに注意してください。 名前付きクラスでは、明示的なコンストラクタ宣言に対して適切なthrows句を宣言することによって、初期化子によってスローされる例外クラスに関する情報を伝播するのはプログラマの責任です。 クラスのイニシャライザによってスローされたチェック済例外クラスと、クラスのコンストラクタによって宣言されたチェック済例外クラスの間のこの関係は、匿名クラス宣言に対して保証されます。これは、明示的ではないためです。コンストラクタ宣言が可能であり、Javaコンパイラは、イニシャライザがスローできるチェックされた例外クラスに基づいて、匿名クラス宣言に適したthrows句を持つコンストラクタを常に生成します。

catch句がチェックされた例外クラスE1を捕捉できる場合にコンパイル時にエラーが発生し、catch句に対応するtryブロックが、E1のサブクラスまたはスーパークラスであるE1またはExceptionのスーパークラスでないかぎり、チェックされた例外クラスをスローできるとはかぎりません。

catch句が例外クラスE1捕捉でき、直前に囲んだtry文の先行するcatch句を捕捉E1またはE1のスーパークラスを捕捉できる場合、コンパイル時にエラーが発生します。

catch句がチェック例外クラスE1を捕捉でき、catch句に対応するtryブロックがチェック例外クラスE2をスローできる場合、Javaコンパイラは警告を発行することをお薦めします。ここで、E2 <: E1および直前に囲んだtry文のcatch句がチェック例外クラスE3をキャッチできます。ここで、E2 <: E3 <: E1をキャッチします。

例11.2.3-1 チェックした例外の捕捉

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

class StaticallyThrownExceptionsIncludeSubtypes {
    public static void main(String[] args) {
        try {
            throw new FileNotFoundException();
        } catch (IOException ioe) {
            // "catch IOException" catches IOException
            // and any subtype.
        }

        try {
            throw new FileNotFoundException();
              // Statement "can throw" FileNotFoundException.
              // It is not the case that statement "can throw"
              // a subtype or supertype of FileNotFoundException.
        } catch (FileNotFoundException fnfe) {
            // ... Handle exception ...
        } catch (IOException ioe) {
            // Legal, but compilers are encouraged to give
            // warnings as of Java SE 7, because all subtypes of
            // IOException that the try block "can throw" have
            // already been caught by the prior catch clause.
        }

        try {
            m();
              // m's declaration says "throws IOException", so
              // m "can throw" IOException. It is not the case
              // that m "can throw" a subtype or supertype of
              // IOException (e.g. Exception).
        } catch (FileNotFoundException fnfe) {
            // Legal, because the dynamic type of the exception
            // might be FileNotFoundException.
        } catch (IOException ioe) {
            // Legal, because the dynamic type of the exception
            // might be a different subtype of IOException.
        } catch (Throwable t) {
            // Can always catch Throwable.
        }
    }

    static void m() throws IOException {
        throw new FileNotFoundException();
    }
}


前述のルールにより、multi-catch句の各代替句(§14.20)は、tryブロックによってスローされた例外クラスをキャッチでき、以前のcatch句ではキャッチされない必要があります。 たとえば、次の2番目のcatch句では、SubclassOfFooが最初のcatch句ですでに捕捉されていると例外分析によって判断されるため、コンパイル時にエラーが発生します。

try { ... }
catch (Foo f) { ... }
catch (Bar | SubclassOfFoo e) { ... }

11.3. 例外の実行時処理

例外がスローされると(§14.18)、例外を処理できるtry文(§14.20)のcatch句(存在する場合)を最も近い動的に囲むcatch句に例外の原因となったコードから制御が転送されます。

文または式は、catch句が属するtry文のtryブロック内に存在する場合、または文または式のコール元がcatch句で動的に囲まれている場合、catch句によって動的に囲まれます。

文または式のコール元は、その発生場所によって異なります。

  • メソッド内の場合、呼出し側はメソッドを呼び出すために実行されたメソッド呼出し式(§15.12)です。

  • コンストラクタ内、インスタンス・イニシャライザ内、またはインスタンス変数のイニシャライザ内では、コール元はクラス・インスタンス作成式(§15.9)またはオブジェクトを作成するために実行されたnewInstanceのメソッド呼出しです。

  • 静的イニシャライザまたはstatic変数のイニシャライザ内では、呼出し側はクラスまたはインタフェースを使用して初期化されるようにした式です(§12.4)。

特定のcatch句が例外を処理できるかどうかは、スローされたオブジェクトのクラスをcatch句の捕捉可能な例外クラスと比較することによって決定されます。 catch句は、その捕捉可能な例外クラスの1つが例外のクラスまたは例外のクラスのスーパークラスである場合、例外を処理できます。

同様に、catch句は、その捕捉可能な例外クラスの1つであるinstanceof (§15.20.2)の例外オブジェクトを捕捉します。

例外がスローされたときに発生する制御転送により、例外を処理できるcatch句が見つかるまで、式(§15.6)および文(§14.1)が突然完了します。その後、そのcatch句のブロックを実行して実行を続行します。 例外の原因となったコードは再開されません。

すべての例外(同期および非同期)は正確です。制御の転送が行われると、実行される文および例外がスローされる時点より前に評価される式のすべての影響が発生したように見える必要があります。 例外がスローされた時点以降に発生する式、文、またはその一部は評価されていない可能性があります。

最適化されたコードが、例外が発生した時点に続くいくつかの式または文を投機的に実行した場合、そのようなコードは、この投機的実行をプログラムのユーザーが表示できる状態から非表示にするように準備する必要があります。

例外を処理できるcatch句が見つからない場合は、現在のスレッド(例外が発生したスレッド)が終了します。 終了前に、すべてのfinally句が実行され、捕捉されない例外が次のルールに従って処理されます。

  • 現在のスレッドに捕捉されない例外ハンドラ・セットがある場合、そのハンドラが実行されます。

  • それ以外の場合は、現在のスレッドの親であるThreadGroupに対してメソッドuncaughtExceptionが呼び出されます。 ThreadGroupとその親ThreadGroupuncaughtExceptionをオーバーライドしない場合、デフォルト・ハンドラのuncaughtExceptionメソッドが呼び出されます。

1つのコード・ブロックが常に別のブロックの後に実行されることが望ましい状況では、他のコード・ブロックが突然完了した場合でも、finally句(§14.20.2)を含むtry文を使用できます。

try-finally文またはtry-catch-finally文内のtryまたはcatchブロックが突然完了すると、一致するcatch句が最終的に見つからない場合でも、例外の伝播中にfinally句が実行されます。

tryブロックが突然完了したためにfinally句が実行され、finally句自体が突然完了した場合、tryブロックが突然完了した理由は破棄され、中断完了の新しい理由がそこから伝播されます。

異常終了および例外の捕捉に関する正確なルールは、§14 (Blocks、 Statements、 and Patterns)の各文の仕様および§15 (Expressions)の式(特に§15.6)で詳細に説明されています。

例11.3-1. 例外のスローおよび捕捉

次のプログラムは、例外クラスTestExceptionを宣言します。 クラスTestmainメソッドは、throwerメソッドを4回起動し、例外を4回のうち3回スローします。 メソッドmaintry文は、スローアがスローする各例外をキャッチします。 throwerの起動が正常に完了したか、突然完了したかに関係なく、何が起こったかを説明するメッセージが出力されます。

class TestException extends Exception {
    TestException()         { super(); }
    TestException(String s) { super(s); }
}

class Test {
    public static void main(String[] args) {
        for (String arg : args) {
            try {
                thrower(arg);
                System.out.println("Test \"" + arg +
                                   "\" didn't throw an exception");
            } catch (Exception e) {
                System.out.println("Test \"" + arg +
                                   "\" threw a " + e.getClass() +
                                   "\n    with message: " +
                                   e.getMessage());
            }
        }
    }
    static int thrower(String s) throws TestException {
        try {
            if (s.equals("divide")) {
                int i = 0;
                return i/i;
            }
            if (s.equals("null")) {
                s = null;
                return s.length();
            }
            if (s.equals("test")) {
                throw new TestException("Test message");
            }
            return 0;
        } finally {
            System.out.println("[thrower(\"" + s + "\") done]");
        }
    }
}

プログラムを実行する場合は、引数を渡します。

divide null not test

次の出力が生成されます。

[thrower("divide") done]
Test "divide" threw a class java.lang.ArithmeticException
    with message: / by zero
[thrower("null") done]
Test "null" threw a class java.lang.NullPointerException
    with message: null
[thrower("not") done]
Test "not" didn't throw an exception
[thrower("test") done]
Test "test" threw a class TestException
    with message: Test message

メソッドthrowerの宣言にはthrows句が必要です。これは、チェック例外クラスであるTestExceptionのインスタンス(§11.1.1)をスローできるためです。 throws句を省略すると、コンパイル時にエラーが発生します。

finally句は、呼出しごとに発生する[thrower(...) done]出力で示されているように、例外が発生するかどうかに関係なく、throwerの呼出しごとに実行されます。