Java の例外処理

例外とは?

例外(Exception) というのは、通常の処理の流れを変えるような特別なイベントのことです。 「通常の処理の流れを変える」というのは、平たく言えば「エラー」の状況のことです。

例えば「あるファイルを開いて、文字を読み込む」ということを想定しているときに、 ファイルが存在しないとか、ファイルのアクセス権の設定がでていていなくて、読み込めないとか、そういう場合です。

エラーの状況が発生したとき、Java では通常、それは例外として検出されます。

例外はランタイムシステムが投げる場合もありますし、 自分で例外オブジェクトを作って投げる (throw する) こともできます。

ちなみに、エラーは必ずしも例外で検出しないといけないことはなくて、状況によっては普通に関数の戻り値がこれこれだったら、エラーとする、という風にもできます。

例外が投げられたら、Java のランタイムシステムはその例外の種類に応じた 例外ハンドラ を探します。 例外ハンドラが見つかれば、そのハンドラが実行されます。

例外ハンドラが見つからない場合は、Java ランタイムはデフォルトの例外ハンドラを実行します。 デフォルトのハンドラではスタックトレースを出力して、プログラムを終了します。

Java の例外処理の基本。try-catch ブロック

例外処理の基本形は次のようになります。

try {
  // try ブロック
} catch (Exception e) {
  // 例外処理
} 

try { ... } ブロックと catch { ... } ブロックがあります。

try { ... } のブロックで、何か処理を行い、そこで例外が発生したら、それに続く catch ブロックで 例外を処理することができます。

詳細は後で書きますが、例外は java.lang.Exception クラスをベースクラスにしたオブジェクトで表されます。

catch { ... } ブロックは複数記述することができます。実行時に発生した例外に対して、 上から順番に型をチェックして、その型のcatch { ... } ブロックが実行されます。

次の例ではひとつの try { ... } ブロックに対して、三つのcatch { ... } ブロックを記述しています。

public class TestApp {
  public static void main(String[] args) {
    try {
      String s = null;
      System.out.println(s.toUpperCase());
    } catch (IndexOutOfBoundsException e) {
      System.out.println("Out of range exception.");
    } catch (NullPointerException e) {
      System.out.println("A NullPointerException was caught.");
      System.out.println(e);
    } catch (Exception e) {
      System.out.println("Unknown exception");
    }
  }
}

はじめが IndexOutOfBoundsException 例外をキャッチするブロック、二つ目が NullPointerException 例外をキャッチするブロック、 それから三つ目がその他全ての例外を受け取る受け皿となるためベースクラスの Exception 型をキャッチするブロックです。

このプログラムを実行すると、次のように出力されます。

A NullPointerException was caught.
java.lang.NullPointerException

try ブロックでわざと null を触り、ランタイムに NullPointerException 例外を投げさせています。 ここでは NullPointerException 用の例外処理ハンドラが設置されていたので、それだけ実行されました。

Java の try-catch-finally ブロック

try-catch ブロックにはさらに、finally ブロックを追加することができます。 finally ブロックは例外が発生する、発生しないにかかわらず、常に実行されます。

try {
	// try ブロック
} catch (Exception e) {
	...
} finally {
	// 常に実行される
}

try { } の部分を実行して、そこで基本的な処理を行います。 例外が発生しなければ、finally { } の部分が実行されて全て終了です。

try { } の部分を実行して、そこで例外が発生すれば、その例外の種類に応じて、catch ブロックが実行され、それから finally ブロックが実行されます。

Java の例外の種類とクラス

例外には大きく分けて、二種類あります。チェック例外 (checked exception) と非チェック例外 (unchecked exception) の二種類です。

例外のタイプ 説明 処理
必須?
非チェック例外
(unchecked exception)
実行時例外
(RuntimeException)

null の参照や配列のインデックス外アクセスなど、プログラムの不具合に起因する例外。

キャッチする(キャッチしてかき消す?)こともできることはできますが、本来は不具合のほうを直すべきもの。

任意
エラー
(Error)

アプリケーション外部の要因によるシステムエラー (ハードウェアの故障などが原因等)

任意
チェック例外
(checked exception)

上記二種類以外の例外。

存在しないファイルを読み込もうとした場合の、 java.io.FileNotFoundException 等、通常は正常復帰可能のもの。

必須

代表的な例外の種類には次のようなものがあります。

名前 概要
非チェック例外
NullPointerException オブジェクトが必要な場合に null に対して操作を試みるなどした場合に発生する例外。
IndexOutOfBoundsException 配列のインデックスなどのインデックスについて、有効な範囲を超えてアクセスした場合に発生する例外。
チェック例外
IOException I/O (入出力) 関連のなんらかの例外が発生したことを示す汎用の例外クラス。
ParseException パース (文字解析) 中になんらかの例外が発生したことを示す例外クラス。
SQLException データベース関連の例外が発生したことを示すための例外クラス。
SOAPException SOAP 関連 (いわゆる XML Web サービス) に関連してなんらかの問題が発生したことを示す例外クラス。
TimeoutException ブロッキング操作 (処理の結果を待つ待ち状態) がタイムアウトによって失敗したことを示す例外クラス。

Java の非チェック例外は「ハードの故障」や「ソフトウェアの不具合」に対応

非チェック例外というのは、発生した場合はかなりヤバイ状況であって、プログラムの不具合が検出されたとか、ハードウェアが壊れたとか、そういう場合の例外です。

この場合はその例外を処理してどうこうする、というのはあまり期待されていなくて、速やかにプログラムをシャットダウンするべきというものです。

上表の通り、java.lang.RuntimeException クラスまたは java.lang.Error クラスから派生したクラスです。

具体的には、 null ポインター例外 (NullPointerException) や配列のインデックスの範囲を超えた場合の例外 (IndexOutOfBoundsException) も、 非チェック例外に属します。

その他、Exception という名前のつかない Error をベースクラスとした、VirtualMachineError などもあります。

Java のチェック例外は「正常でも起こりうる復帰可能なエラー」

チェック例外というのは、ファイルを開こうとしたらそのファイルがなかったとか、プログラムの不具合とは関係なく、 状況によっては発生しうる状況とされるものです。

非チェック例外の処理は任意ですが、チェック例外は必ず処理しないといけないことになっています。

チェック例外は java.lang.Exception クラスから派生します。

Java の例外クラスの定義

例外クラスは、自分で定義して作成することもできます。

自分で例外クラスを定義するときには、上で説明したように、例外には大きく2種類あることに注意しましょう。

チェック例外とするクラスは java.lang.Exception クラスから派生します。

非チェック例外とするクラスは java.lang.RuntimeException クラスまたは java.lang.Error クラスから派生します。

Java の例外オブジェクトを作って投げる

エラーの状況で、プログラムの呼び出し側に例外処理を行わせたい場合は、throw で例外オブジェクトを投げることができます。

throw new Exception("Hello! Something went wrong!");

Exception クラスの例外オブジェクトを投げています。コンストラクタに渡している文字列は、例外をキャッチしたところで getMessage() メソッドで取り出せます。

このとき、例外を投げるメソッド内でその例外を処理しない場合は、そのメソッドの呼び出し側に例外が投げられる可能性のあるメソッドであることを明示する為に、 メソッドの定義時に throws キーワードを用いて、投げられる例外の種類を明記します。

特にチェック例外の場合は throws キーワードで投げる例外を明示しなければいけません。非チェック例外の場合は任意です。

例えば次のように Exception クラスから派生して FooException を定義したとします。

public class FooException extends Exception {
	//...
}

Exception クラスから派生した例外クラスはチェック例外に分類されますから、必ずプログラムで処理しなければなりません。

FooException 例外を投げて、そのメソッド内で処理しない場合は次のように throws キーワードと投げる例外の種類を、 メソッド名に並べて書く必要があります。(複数ある場合はカンマ区切りで並べます)

  public static void foo() throws FooException {
    throw new FooException();
  }

このように書くことで、foo() メソッドを呼ぶ場合は例外処理が必須であることが明示的に記述されます。

以上、Java の例外について説明しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Java 入門