Java超|Throwableと例外クラス

例外の正体がクラスのオブジェクトだと分かると、catch の意味がすっと見えてくる。Javaの例外処理は、トラブルを種類ごとに分類し、適切な受け皿で受け止めるためのしくみです。

これまで、例外は「実行中に起きるトラブル」として見てきました。

たとえば、配列の範囲外へアクセスしたときに ArrayIndexOutOfBoundsException が発生する、というような形です。

この理解だけでも、try-catch の基本はかなり分かります。

しかし、さらに一歩進んで考えると、例外は単なるエラーメッセージではありません。
Javaでは、例外も クラスから作られたオブジェクト として扱われます。

ここが分かると、catch の丸かっこの中にクラス名を書く理由が見えてきます。

catch(ArrayIndexOutOfBoundsException e) {
    ...
}

この書き方は、ただのおまじないではありません。

ArrayIndexOutOfBoundsException という例外クラスのオブジェクトを受け取る という意味を持っています。

ドラゴンボール風にたとえると、任務中に起きるトラブルを、すべて「異常事態」とひとまとめにするだけではありません。

任務中のトラブルJavaでの例外のイメージ
戦士名簿の存在しない欄へ記録しようとするArrayIndexOutOfBoundsException
カプセル記録や通信ログの読み書きに失敗するIOException
戦闘力として読めない札を数値にしようとするNumberFormatException
重大すぎて現場では対応しにくい異常Error 系列

このように、例外には種類があります。
そして、その種類はクラスとして整理されています。

つまり、例外処理は特別な魔法のような仕組みではなく、Javaのクラスと継承の考え方の上に成り立っています。

ドラゴンボール風に言えば、警告札にも系統があります。

戦士名簿の範囲外を知らせる警告札。
カプセル記録の読み書き失敗を知らせる警告札。
任務続行が難しい重大異常を知らせる警告札。

それぞれがバラバラに存在しているのではなく、上位の分類から枝分かれした仲間として整理されています。

この見方ができるようになると、try-catch の理解がかなり深まります。

例外はクラスのオブジェクトである

Javaで扱う例外は、単なる文字列ではありません。

例外は、Throwable 系列のクラスから作られるオブジェクトです。

たとえば、これまで何度も出てきた ArrayIndexOutOfBoundsException も、例外専用のクラスです。

配列の範囲外へアクセスしたとき、Javaはその問題を表す ArrayIndexOutOfBoundsException オブジェクトを作ります。

そして、そのオブジェクトが catch に渡されます。

つまり、catch は「エラーっぽい何か」をぼんやり受け取っているわけではありません。

指定した例外クラスに対応するオブジェクトを受け取っている のです。

ドラゴンボール風にたとえると、任務中に警告札が飛んでくる場面を考えると分かりやすいです。

ドラゴンボール風のイメージJavaでの意味
範囲外警告の札が発生するArrayIndexOutOfBoundsException オブジェクトが作られる
支援役がその札を受け取るcatch が例外オブジェクトを受け取る
札の内容を確認する変数 e を使って例外情報を見る
札の種類に応じて対応する例外クラスごとに catch を分ける

このように考えると、例外はただ画面に表示されるエラー文ではなく、Javaの中で扱われるオブジェクトだと分かります。

catchの中で何を受け取っているのか

catch の基本形をもう一度見てみます。

catch(ArrayIndexOutOfBoundsException e) {
    ...
}

この丸かっこの中には、2つの意味があります。

部分意味
ArrayIndexOutOfBoundsException受け取りたい例外のクラス
e受け取った例外オブジェクトを指す変数

つまり、この catch は、次の意味になります。

ArrayIndexOutOfBoundsException 型の例外オブジェクトを受け取り、それを e という名前で扱う

ここでの e は、普通の変数名です。

int x の x や、String name の name と同じように、例外オブジェクトを指すための変数名です。

catch(ArrayIndexOutOfBoundsException e)

この e を使うと、catch の中で例外の情報を表示できます。

たとえば、次のように書けます。

System.out.println(e + " が発生しました。");

この e には、実際に発生した例外オブジェクトが入っています。
そのため、出力すると例外の種類や詳細情報を確認できます。

ドラゴンボール風にたとえると、catch の e は、支援役が受け取った警告札そのものを指しているようなものです。

ただ「警告が来ました」と言うだけではなく、
「どんな警告なのか」
「どのような内容なのか」
を確認できる札を持っているイメージです。

受け取れるのはThrowable系列の仲間だけ

catch で受け取れるものは、何でもよいわけではありません。

Javaの catch で受け取れるのは、Throwable クラス、またはそのサブクラスのオブジェクトです。

つまり、例外として扱えるのは、Throwable 系列に属しているクラスだけです。

catch(ArrayIndexOutOfBoundsException e)

この書き方ができるのは、ArrayIndexOutOfBoundsException が Throwable 系列のクラスだからです。

反対に、まったく関係のない普通のクラスを catch に書くことはできません。

ドラゴンボール風にたとえると、任務中の警告札受付では、警告札として認められた系統の札だけを受け取れるようなものです。

受け取れるもの理由
Throwable例外やエラーの大もとのクラス
ExceptionThrowable の仲間
RuntimeExceptionException の仲間
ArrayIndexOutOfBoundsExceptionRuntimeException の仲間
関係のない普通のクラスThrowable 系列ではないため catch できない

catch は、Throwable 系列の警告オブジェクトを受け止めるための場所です。

この前提があるから、例外処理はクラス構造と深くつながっています。

図:例外はThrowable系列のオブジェクト

この図が示していること

この図では、配列の範囲外アクセスによって発生した例外が、例外オブジェクトとして catch に渡される流れを表しています。

power[10] = 9000; によって、ArrayIndexOutOfBoundsException オブジェクトが作られます。

その例外オブジェクトは、catch(ArrayIndexOutOfBoundsException e) に渡され、e という変数で扱えるようになります。

図の要素意味
power[10] = 9000例外が発生する処理
ArrayIndexOutOfBoundsException オブジェクト発生した例外そのもの
catch(ArrayIndexOutOfBoundsException e)例外を受け取る場所
e受け取った例外オブジェクトを指す変数
Throwable 系列catch で受け取れる例外クラスの系統

この図から分かることは、例外が単なる表示メッセージではなく、クラスから作られたオブジェクトとして扱われているということです。

例外クラスの全体像

例外クラスは、親子関係を持った階層構造で整理されています。

大まかには、次のような構造です。

Throwable
├─ Error
└─ Exception
   ├─ IOException
   └─ RuntimeException
      └─ ArrayIndexOutOfBoundsException

表で整理すると、次のようになります。

階層代表例意味
Throwableすべての土台例外や重大なエラーの大もと
ErrorOutOfMemoryError など通常はプログラム側で対処しにくい深刻な問題
ExceptionIOException など一般的な例外処理の対象
RuntimeExceptionArrayIndexOutOfBoundsException など実行中に起きやすい例外
ArrayIndexOutOfBoundsException配列範囲外アクセス配列の添字が範囲外だったときの例外

この構造を見ると、ArrayIndexOutOfBoundsException は突然出てきた単独の存在ではないことが分かります。

ArrayIndexOutOfBoundsException は、RuntimeException の仲間です。
RuntimeException は、Exception の仲間です。
Exception は、Throwable の仲間です。

つまり、ArrayIndexOutOfBoundsException は、上位の分類をたどっていける例外クラスです。

ドラゴンボール風にたとえると、悟空が個人として存在しているだけではなく、亀仙流の戦士、地球を守る戦士、戦士本部の仲間というように、上位の分類をたどれるイメージです。

Throwableは何を表すのか

Throwable は、例外やエラーを表すクラス階層の一番上にある大きな土台です。

Javaでは、実行中に何か異常が起きたとき、その情報を Throwable 系列のオブジェクトとして扱います。

Throwable は、例外処理の世界でいう最上位の分類です。

ドラゴンボール風にたとえると、任務中に発生するすべての異常札をまとめる大分類です。

Throwable のイメージ内容
大もとの警告札例外や重大エラーをまとめる土台
すべての異常情報の親Error や Exception につながる
catch の対象になる系統Throwable の仲間だけが例外として扱える

ただし、普段の学習でよく扱うのは、Throwable そのものより、その下にある Exception 系列です。

ErrorとExceptionの違い

Throwable の下には、大きく分けて Error と Exception があります。

この2つの違いはとても大切です。

クラス意味基本的な考え方
Error継続が難しい深刻な問題通常は自分で積極的に処理しない
Exceptionプログラムで対処を考える問題try-catch で扱うことが多い

Error は、システム資源が足りないなど、プログラム側の通常の処理では対処しにくい深刻な問題を表すことがあります。

一方、Exception は、プログラムで対応を考える対象です。

たとえば、ファイルが見つからない、配列の範囲外にアクセスした、数値変換に失敗した、というような問題です。

ドラゴンボール風に言うと、Exception は任務中に支援役が対応できる可能性があるトラブルです。

一方、Error は、重力修行室そのものが崩壊しそうになっている、戦士本部全体の制御装置が壊れかけている、というような現場の一隊だけでは対応しにくい重大異常に近いです。

RuntimeExceptionとは何か

Exception の中には、RuntimeException というグループがあります。

RuntimeException は、実行中に起きやすい例外をまとめる系統です。

今回使う ArrayIndexOutOfBoundsException も、RuntimeException の仲間です。

階層としては、次のようにつながっています。

Throwable
└─ Exception
   └─ RuntimeException
      └─ ArrayIndexOutOfBoundsException

このつながりを意識すると、catch でどのクラスを指定するかによって、受け止められる範囲が変わることも分かりやすくなります。

catch に書く型受け止められる範囲のイメージ
ArrayIndexOutOfBoundsException配列の範囲外アクセスだけを細かく受け止める
RuntimeException実行時例外を広めに受け止める
ExceptionException 系列をさらに広く受け止める

細かい型を指定すれば、原因に合わせた具体的な対応を書きやすくなります。

広い型を指定すれば、複数種類の例外をまとめて受け止めやすくなります。

図:例外クラスの階層構造

この図が示していること

この図は、Javaの例外がクラス階層で整理されていることを表しています。

一番上に Throwable があり、その下に Error と Exception が分かれています。

さらに Exception の下に IOException や RuntimeException があり、RuntimeException の下に ArrayIndexOutOfBoundsException があります。

図の要素意味
Throwable例外やエラーの大もと
Error通常は現場の処理で対応しにくい深刻な問題
Exceptiontry-catch で扱うことが多い例外
RuntimeException実行中に起きやすい例外
ArrayIndexOutOfBoundsException配列の範囲外アクセスを表す例外

この図から分かることは、catch に書くクラス名は、受け取りたい例外の種類を指定しているということです。

例外情報の出力を確認する

ここでは、catch で受け取った例外オブジェクトを使って、例外の情報を表示する例を見ていきます。

5人分の修行名簿を用意し、存在しない10番目に戦闘力を記録しようとします。

その結果、ArrayIndexOutOfBoundsException が発生します。

catch では、その例外を e という変数で受け取り、e を出力して例外情報を確認します。

ファイル名:Sample4.java

class Sample4
{
    public static void main(String[] args)
    {
        try {
            int[] power;
            power = new int[5];

            System.out.println("戦士本部の修行名簿の10番目に戦闘力を記録します。");

            power[10] = 9000;
            System.out.println("10番目への戦闘力登録が完了しました。");
        }
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("記録できる範囲をこえています。");
            System.out.println(e + " が発生しました。");
        }

        System.out.println("修行記録の確認を終えました。");
    }
}

Sample4.javaの処理の流れ

このプログラムでは、try の中で配列 power を作成しています。

int[] power;
power = new int[5];

new int[5] によって、5個分の int 型データを入れる場所が用意されます。

Javaの配列は、0 から数えます。

配列の長さ使える添字
50, 1, 2, 3, 4

そのため、power[0] から power[4] までは使えます。

しかし、次の行では power[10] に値を入れようとしています。

power[10] = 9000;

これは範囲外アクセスです。

そのため、ArrayIndexOutOfBoundsException が発生します。

例外が発生すると、try の中の次の成功メッセージは実行されません。

System.out.println("10番目への戦闘力登録が完了しました。");

その後、対応する catch に処理が移ります。

catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("記録できる範囲をこえています。");
    System.out.println(e + " が発生しました。");
}

ここで e は、発生した ArrayIndexOutOfBoundsException オブジェクトを指しています。

実行結果

実行結果は、次のようになります。

戦士本部の修行名簿の10番目に戦闘力を記録します。
記録できる範囲をこえています。
java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5 が発生しました。
修行記録の確認を終えました。

出力内容は、Javaの実行環境によって細かい表現が少し変わることがあります。

大切なのは、e を表示すると、例外の種類や詳細情報を確認できるという点です。

出力どこで実行されたか
戦士本部の修行名簿の10番目に戦闘力を記録します。try
記録できる範囲をこえています。catch
java.lang.ArrayIndexOutOfBoundsException... が発生しました。catch で e を出力
修行記録の確認を終えました。try-catch の後ろ

try の中で例外は起きています。
しかし、catch が受け止めているため、最後の確認メッセージまで処理が続いています。

eを表示すると何が分かるのか

catch の中では、次の行で e を表示しています。

System.out.println(e + " が発生しました。");

この e は、catch で受け取った例外オブジェクトです。

そのため、e を表示すると、例外の情報を確認できます。

e から分かること内容
例外の種類何の例外が起きたか
詳細情報どのような状況だったか
原因特定の手がかりどこを見直せばよいかのヒント

今回なら、ArrayIndexOutOfBoundsException と表示されることで、配列の範囲外アクセスが原因だと分かります。

さらに、Index 10 out of bounds for length 5 という内容から、長さ5の配列に対して添字10を使ったことが分かります。

ドラゴンボール風にたとえると、警告札に次のように書かれている状態です。

警告札の情報Javaの例外情報
警告の種類ArrayIndexOutOfBoundsException
問題の内容10番目は範囲外
名簿の長さ5人分
対応のヒント添字の範囲を見直す

ただ「失敗しました」と言われるより、どの種類の失敗なのかが分かるため、原因を探しやすくなります。

図:発生した例外オブジェクトがeに渡される流れ

この図が示していること

この図は、try の中で発生した例外オブジェクトが、catch の変数 e に渡される流れを表しています。

power[10] = 9000; によって配列の範囲外アクセスが発生します。

すると、ArrayIndexOutOfBoundsException オブジェクトが作られ、catch の e に渡されます。

図の要素意味
power[10] = 9000例外が発生する場所
ArrayIndexOutOfBoundsException オブジェクト発生した例外そのもの
catch(ArrayIndexOutOfBoundsException e)例外オブジェクトを受け取る場所
e受け取った例外オブジェクトを指す変数

この図から分かることは、catch の e はただの飾りではなく、実際に発生した例外オブジェクトを扱うための変数だということです。

例外の種類ごとに処理を分けられる理由

catch ブロックは1つだけとは限りません。

例外の種類ごとに複数の catch を並べることができます。

たとえば、考え方としては次のようになります。

catch(ArrayIndexOutOfBoundsException ae) {
    配列の範囲外に対する処理
}
catch(IOException ie) {
    入出力エラーに対する処理
}

このように処理を分けられるのは、例外がクラスとして分類されているからです。

ArrayIndexOutOfBoundsException は配列の範囲外アクセスを表します。
IOException は入出力関連の問題を表します。

それぞれ別の例外クラスなので、catch を分けることで、状況に応じた対応を書けます。

ドラゴンボール風にたとえると、次のような役割分担です。

トラブルの種類対応班
修行名簿の範囲外指定名簿管理の支援役
カプセル記録の読み書き失敗記録係の支援役
戦闘力札の読み取り失敗計測係の支援役

任務中のトラブルをすべて同じ対応にするのではなく、種類に応じて担当を分けられるわけです。

親クラスで広く受け止めることもできる

例外はクラスの継承関係で整理されているため、親クラスで広く受け止めることもできます。

代表的なのが Exception です。

catch(Exception e) {
    例外全体に対する共通処理
}

Exception は、多くの例外クラスの親にあたります。

そのため、Exception 型の catch は、その下にある多くの例外を受け止められます。

catch の指定受け止める範囲
ArrayIndexOutOfBoundsException配列の範囲外アクセスを細かく受け止める
RuntimeExceptionRuntimeException 系列を広めに受け止める
ExceptionException 系列をさらに広く受け止める

ドラゴンボール風にたとえると、個別の担当を置くのではなく、戦士本部の総合支援室で広く警告札を受け取るようなものです。

ただし、広く受け止めるほど、具体的な原因に合わせた対応は書きにくくなることがあります。

たとえば、配列の範囲外なら「添字を確認してください」と言えます。
カプセル記録の読み込み失敗なら「ファイルの場所を確認してください」と言えます。

しかし、すべて Exception でまとめると、細かい原因に応じたメッセージを分けにくくなります。

受け止め方メリット注意点
個別の例外クラスで受け止める原因ごとに具体的な対応を書けるcatch が増える
親クラスで広く受け止めるまとめて処理しやすい原因ごとの細かい対応が薄くなる

そのため、どの例外にどんな対応をしたいのかを考えて、catch の型を選ぶことが大切です。

catchを複数書くときの考え方

catch を複数書く場合は、例外の種類ごとに処理を分けられます。

ただし、例外には親子関係があります。

そのため、広い型を先に書くと、後ろの細かい型に届かなくなる場合があります。

たとえば、Exception は ArrayIndexOutOfBoundsException の親側にある広い型です。

そのため、考え方としては、より具体的な例外を先に、広い例外を後に置くのが基本です。

catch(ArrayIndexOutOfBoundsException e) {
    配列の範囲外に対する処理
}
catch(Exception e) {
    その他の例外に対する処理
}

ドラゴンボール風に言えば、まず「名簿範囲外の専門担当」が対応し、それ以外は「戦士本部の総合担当」が受け止めるような流れです。

順番役割
先に具体的な catch原因に合った専門対応をする
後に広い catchその他をまとめて受け止める

この考え方も、例外がクラス階層で整理されているからこそ成り立ちます。

例外処理はクラスと継承のしくみでできている

例外処理は、特別な書き方に見えるかもしれません。

しかし、中心にあるのはJavaのクラスと継承です。

例外は、Throwable 系列のクラスから作られるオブジェクトです。
ArrayIndexOutOfBoundsException も、RuntimeException のサブクラスであり、Exception の仲間です。

catch は、その例外オブジェクトを受け取るための場所です。

catch(ArrayIndexOutOfBoundsException e)

この書き方は、次のように読み取れます。

書き方意味
ArrayIndexOutOfBoundsException受け取りたい例外の型
e受け取った例外オブジェクトを指す変数
catch の中その例外に対する処理を書く場所

つまり、例外処理は「何かよく分からないエラーを捕まえる」ものではありません。

どのクラスの例外オブジェクトを受け取るのかを指定し、そのオブジェクトを使って対応する仕組みです。

例外情報を見ることの大切さ

catch の中で e を表示することには、大切な意味があります。

例外が発生したとき、単に「失敗しました」とだけ表示しても、原因が分かりにくいです。

しかし、e を出力すると、例外の種類や詳細情報が見えます。

System.out.println(e + " が発生しました。");

今回の例なら、次のような情報が分かります。

情報内容
例外名ArrayIndexOutOfBoundsException
問題の添字10
配列の長さ5
原因の方向性配列の範囲外アクセス

この情報があれば、power[10] の指定を見直す必要があると判断できます。

ドラゴンボール風に言えば、警告札に「名簿10番目は範囲外、名簿は5人分」と書かれているようなものです。

どこを見るべきかがはっきりするので、修正しやすくなります。

例外クラスの構造で押さえておきたいこと

例外処理とクラス構造を学ぶときは、次の点を押さえると理解しやすくなります。

ポイント内容
例外の正体Throwable 系列のクラスから作られるオブジェクト
catch のクラス名受け取りたい例外の種類を指定する
catch の変数受け取った例外オブジェクトを指す
e の出力例外の種類や詳細情報を確認できる
Error と ExceptionThrowable の下にある大きな分類
RuntimeException実行中に起きやすい例外の系統
個別の catch例外ごとに具体的な対応を書ける
親クラスの catch広い範囲の例外をまとめて受け止められる

例外処理は、ただエラーを避けるための書き方ではありません。

Javaのクラスと継承の仕組みを使って、起きた問題を分類し、その種類に合った処理へつなげるためのしくみです。

ドラゴンボール風に言えば、任務中の警告札を種類ごとに分類し、名簿担当、記録担当、総合支援担当へ渡していくようなものです。

例外がクラスであり、catch がその例外オブジェクトを受け取っている。
この感覚がつかめると、try-catch の書き方は丸暗記ではなくなります。

なぜ catch にクラス名を書くのか。
なぜ e を使えるのか。
なぜ Exception で広く受け止められるのか。

それぞれが、Javaのクラス構造と自然につながって理解できるようになります。