Java超|try-catchによる例外処理

予想外のトラブルも、try-catch があれば落ち着いて受け止められる。Javaの例外処理は、危険な任務に備える戦士本部の安全装置です。

プログラムを作っていると、いつも思いどおりに処理が進むとは限りません。

文法としては正しく書けていても、実行してみると、想定外の値が使われたり、存在しない場所へアクセスしてしまったりして、そのままでは処理を続けられないことがあります。

たとえば、5人分しか用意していない配列に対して、10番目の場所へ値を入れようとするような場合です。

このような実行中のトラブルに対応するために、Javaには 例外処理 というしくみがあります。
その基本になるのが try-catch です。

try-catch は、簡単に言えば、危険が起きるかもしれない処理と、問題が起きたときの対応を分けて書くためのしくみです。

ブロック役割
try例外が起きるかもしれない処理を書く場所
catch例外が起きたときに受け止めて対応する場所

ドラゴンボール風にたとえると、危険な惑星へ悟空たちが任務に出る前に、ピッコロの支援、ブルマの通信装置、仙豆、退避ルートを用意しておくようなものです。

何も備えがないまま任務中に問題が起きると、その場で任務は中断してしまいます。
しかし、あらかじめ「この種類のトラブルが起きたら、ここで受け止める」と決めておけば、問題が起きても状況を確認し、次の処理へ進める可能性があります。

Javaの try-catch も同じです。

危険が起きるかもしれない処理を try に入れます。
実際に例外が起きたら、対応する catch がそれを受け止めます。
そして、catch で必要な処理をしたあと、try-catch の後ろの処理へ進めます。

この考え方を身につけると、エラーで突然止まるだけのプログラムから、トラブルに対応できるプログラムへ一歩進めるようになります。

例外処理とは何か

例外処理とは、実行中に問題が起きたときに、あらかじめ決めておいた方法で対応することです。

前に学んだように、配列の範囲をこえるような処理をすると、例外が発生します。

たとえば、次のような状態です。

状況Javaで起きること
配列は5個分しかない使える添字は 0 から 4
10番目に値を入れようとする範囲外アクセスになる
対応がないプログラムが途中で終了する
try-catch があるcatch で受け止めて対応できる

ドラゴンボール風に考えると、悟空たちの戦士本部にある修行名簿に、5人分の欄しかないのに、10番目の欄へ戦闘力を書き込もうとしている状態です。

何も備えがなければ、名簿管理の処理はそこで止まります。

しかし、支援担当のピッコロやブルマの警報システムが待機していて、

「その番号は使えません。登録できる範囲を確認してください」

と受け止めてくれれば、任務全体をいきなり終了させずに、状況を知らせることができます。

これが例外処理の考え方です。

例外処理は、問題をなかったことにする仕組みではありません。
問題が起きたときに、どう受け止め、どう知らせ、どこまで処理を続けるかを決める仕組みです。

try と catch の役割

try-catch は、try ブロックと catch ブロックで構成されます。

基本の形は次のようになります。

try {
    例外が起きるかもしれない処理
}
catch(例外の種類 変数名) {
    例外が起きたときの処理
}

それぞれの役割を整理すると、次のようになります。

ブロック役割ドラゴンボール風のイメージ
tryまず実行してみる処理を書く危険の可能性がある任務を進める場所
catch例外が起きたときの対応を書く支援役が警告を受け止める場所

try は「ここで問題が起きるかもしれないけれど、まず処理を実行してみる」という場所です。

catch は「もし指定した種類の例外が起きたら、ここで受け止めて対応する」という場所です。

ドラゴンボール風に言えば、try は悟空たちが実際に任務を進める場面です。
catch は、異常が起きたときにピッコロやブルマの支援システムが警告を受け止める場面です。

try-catch の基本の流れ

try-catch の処理は、次のような流れで進みます。

順番処理の流れ
1try ブロックの中の処理を上から順に実行する
2try の途中で例外が起きる
3例外が起きた地点で try の後続処理は中断される
4例外の種類に合う catch ブロックを探す
5一致する catch があれば、その中の処理を実行する
6catch の処理後、try-catch の後ろへ進む

ここで大切なのは、try の中で例外が起きた場合、try の残りの文がそのまま続けて実行されるわけではないということです。

例外が起きた場所で、try の通常の流れはいったん止まります。
そして、対応する catch へ処理が移ります。

図:try-catch の基本の流れ

この図が示していること

この図は、try の中で起きた例外が catch に渡される流れを表しています。

try では、危険が起きるかもしれない処理を実行します。
その途中で例外が発生すると、try の後続処理はいったん中断されます。

その後、例外の種類に合う catch が見つかると、その catch の中の処理が実行されます。

図の要素意味
try例外が起きるかもしれない処理
例外発生処理を続けられない問題
catch例外を受け止める場所
後続処理catch のあとに進む処理

この図から分かることは、try-catch は例外を消す仕組みではなく、例外が起きたときの流れを決める仕組みだということです。

try-catch 処理を確認する

ここでは、配列の範囲をこえる例外を try-catch で処理する例を見ていきます。

5人分の戦闘力を記録できる配列を用意します。
しかし、あえて 10番目 に戦闘力を登録しようとします。

通常なら、配列の範囲外アクセスでプログラムが途中で止まります。

今回は、その危険な処理を try に入れ、ArrayIndexOutOfBoundsException が起きたときの対応を catch に書きます。

ファイル名:Sample2.java

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

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

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

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

このプログラムの全体像

このプログラムでは、try ブロックの中に、例外が起きる可能性のある処理を書いています。

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

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

    power[10] = 9000;
    System.out.println("10番目に戦闘力9000を登録しました。");
}

配列 power は、次のように作成されています。

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

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

ただし、Javaの配列の添字は 0 から始まります。

配列の要素数使える添字
50, 1, 2, 3, 4

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

しかし、try の中では次のように書いています。

power[10] = 9000;

これは、存在しない10番目の場所に値を入れようとしている状態です。

ドラゴンボール風にたとえると、5人分しかない修行名簿に、10番目の戦士の戦闘力を書き込もうとしているようなものです。

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

catch が例外を受け止める

例外が発生すると、対応する catch ブロックへ処理が移ります。

catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("登録できる範囲をこえています。");
}

この catch の丸かっこの中には、次の2つが書かれています。

部分意味
ArrayIndexOutOfBoundsException受け取りたい例外の種類
e受け取った例外を入れる変数名

今回発生する例外は ArrayIndexOutOfBoundsException です。
catch も ArrayIndexOutOfBoundsException を受け取る形になっています。

そのため、例外の種類が一致し、catch の中の処理が実行されます。

ドラゴンボール風に言えば、
「名簿の範囲外に記録しようとしたときの担当支援役」
が待機していて、その警告を受け止めるイメージです。

実行結果

このプログラムを実行すると、次のようになります。

戦士本部の修行名簿の10番目に戦闘力を登録します。
登録できる範囲をこえています。
修行記録の確認を終えました。

この実行結果で注目したいのは、最後のメッセージが表示されていることです。

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

この文は、try-catch の後ろに書かれています。

例外は確かに発生しています。
しかし、catch がそれを受け止めたため、try-catch の後ろの処理へ進めています。

出力なぜ表示されたか
戦士本部の修行名簿の10番目に戦闘力を登録します。例外発生前に実行された
登録できる範囲をこえています。catch で表示された
修行記録の確認を終えました。catch の後に処理が続いた

try-catch を使うことで、プログラムは「例外が起きたら即終了」ではなく、「例外を受け止めて、その後の処理へ進む」形になります。

try の中で例外が起きた後の文は実行されない

try ブロックの中には、次の文があります。

System.out.println("10番目に戦闘力9000を登録しました。");

しかし、この文は実行されません。

なぜなら、その直前で例外が発生しているからです。

power[10] = 9000;

try の中で例外が起きると、その時点で try ブロックの通常処理は中断されます。
その後、対応する catch へ移動します。

つまり、try の中で例外が起きた場合、例外発生行より後ろの文は飛ばされます。

try 内の処理実行されるか
配列 power を作る実行される
10番目に登録します と表示する実行される
power[10] = 9000;ここで例外が発生する
10番目に戦闘力9000を登録しました と表示する実行されない

ドラゴンボール風にたとえると、名簿に記録している途中で存在しない欄を指定してしまい、支援役の対応へ切り替わった状態です。

そのため、
「登録しました」
という成功報告には進めません。

ここは、try-catch を理解するうえでとても重要です。

Sample2.java の処理の流れ

今回のプログラムの流れを、順番に整理してみましょう。

順番処理実際の動き
1try に入る例外が起きる可能性のある処理を始める
2配列を作るpower = new int[5]; が実行される
3登録開始メッセージを表示戦士本部の修行名簿の10番目に戦闘力を登録します。
4power[10] に代入しようとする範囲外なので例外が発生する
5try の残りを中断登録成功メッセージは表示されない
6catch に移動ArrayIndexOutOfBoundsException を受け止める
7catch のメッセージを表示登録できる範囲をこえています。
8try-catch の後ろへ進む修行記録の確認を終えました。

この流れを見ると、try-catch の役割がはっきりします。

try は、危険が起きるかもしれない処理を実行する場所です。
catch は、実際に起きた例外を受け止める場所です。
catch の処理が終わると、try-catch の外側へ進みます。

図:Sample2.java の処理の流れ

この図が示していること

この図は、Sample2.java の処理がどの順番で進むかを表しています。

try の中では、配列を作り、メッセージを表示し、power[10] へ代入しようとします。
しかし、power[10] は範囲外なので、そこで ArrayIndexOutOfBoundsException が発生します。

そのため、try の中にある登録成功メッセージは実行されません。
その後、catch が例外を受け止め、最後に try-catch の後ろの処理へ進みます。

図のポイント内容
power[10]例外が発生する場所
登録成功メッセージ例外発生後なので実行されない
catch例外を受け止める
try-catch の後ろcatch の後に実行される

この図から分かることは、try-catch を使っても、try の残りがすべて実行されるわけではないということです。

catch の丸かっこの意味

catch の丸かっこには、どの種類の例外を受け止めるかを書きます。

今回のコードでは、次のようになっています。

catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("登録できる範囲をこえています。");
}

この部分を分解すると、次のようになります。

部分意味
ArrayIndexOutOfBoundsException受け止める例外の種類
e受け取った例外を入れる変数名
catch の中身例外が起きたときに実行する処理

ArrayIndexOutOfBoundsException は、配列の範囲外アクセスが起きたときの例外です。

今回の power[10] = 9000; では、配列の範囲外にアクセスしているため、この例外が発生します。

catch に書かれた例外の種類と、実際に発生した例外の種類が合っているため、catch の処理が実行されます。

ドラゴンボール風にたとえると、catch の丸かっこは「どの種類の任務トラブルを担当するか」を表しています。

任務トラブルJavaの例外
名簿の範囲外に記録しようとしたArrayIndexOutOfBoundsException
数字ではない札を数値として読もうとしたNumberFormatException
存在しない巻物を開こうとしたファイル関連の例外

今回の catch は、名簿の範囲外トラブルを担当する支援役です。

catch で受け取るとはどういうことか

例外が起きて、それに対応する catch ブロックが処理を行うことを、catch で受け取ると考えると分かりやすいです。

これは、単にエラーメッセージを表示するだけではありません。

プログラムにとっては、

この種類の問題が起きたら、ここで対応する

という受け皿が用意されている状態です。

今回なら、配列の範囲外アクセスが起きたとき、次の catch が受け止めます。

catch(ArrayIndexOutOfBoundsException e)

そして、利用者に分かりやすいメッセージを表示します。

System.out.println("登録できる範囲をこえています。");

何も対策していない場合、Javaの長い例外メッセージが表示され、プログラムが途中で終了してしまいます。

しかし、catch があることで、利用者に合わせた説明を表示できます。

対応なしtry-catch あり
例外が表示されて途中終了しやすいcatch が受け止めて対応できる
利用者には原因が分かりにくい分かりやすいメッセージを出せる
後続処理に進みにくいcatch 後に後続処理へ進める

ドラゴンボール風に言えば、危険な警報が鳴ったときに、ブルマの解析システムがその内容を分かりやすく言い換えてくれるようなものです。

catch がなかったらどうなるのか

もし Sample2.java に catch がなかった場合、power[10] = 9000; のところで例外が発生し、そのままプログラムは途中で終了します。

つまり、次の最後のメッセージには進めません。

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

main メソッドは、プログラムの開始地点です。

main メソッドの中で例外が発生し、それを受け止める catch が見つからない場合、プログラムはそれ以上処理を続けにくくなります。

状態結果
catch がない例外が発生した場所でプログラムが終了しやすい
catch がある例外を受け止めて対応できる
catch の後ろに処理があるcatch のあとに実行できる

try-catch は、ただエラー表示を変えるためだけのものではありません。

問題が起きたときに、処理全体をどう続けるかを決めるための大切な仕組みです。

try-catch を使う意味

try-catch を使う意味は、とてもシンプルです。

問題が起きても、そこで終わりにしないためです。

今回のプログラムでは、配列の範囲外アクセスという問題が起きています。
しかし、try-catch を使ったことで、次のような利点が生まれています。

利点内容
問題を受け止められるArrayIndexOutOfBoundsException を catch できる
分かりやすいメッセージを出せる登録できる範囲をこえています。 と表示できる
後続処理へ進める修行記録の確認を終えました。 まで実行できる
プログラムが急に終わりにくい例外発生時の対応を決められる

ドラゴンボール風にたとえると、try-catch は任務中の安全装置です。

危険な任務に何の備えもなく向かえば、想定外の事態で流れが止まってしまいます。
しかし、支援役や退避手順を用意しておけば、問題が起きても状況を受け止めて立て直せます。

Javaのプログラムでも、例外が起きそうな処理には try-catch を使うことで、より利用者にやさしい動きにできます。

図:try-catch がある場合とない場合

この図が示していること

この図は、try-catch がない場合とある場合の違いを表しています。

catch がない場合、例外が発生すると、その場で処理が止まりやすくなります。
一方、try-catch がある場合は、catch が例外を受け止め、分かりやすいメッセージを表示したあと、後続処理へ進めます。

比較catch なしtry-catch あり
例外発生時その場で止まりやすいcatch が受け止める
利用者への説明Javaの例外メッセージが出る分かりやすいメッセージを出せる
後続処理進みにくい進める
プログラムの印象急に止まる落ち着いて対応できる

この図から分かることは、try-catch はプログラムを例外に強くするための安全装置だということです。

try-catch で特に大切なポイント

try-catch を学ぶときは、次の点をしっかり押さえると理解しやすくなります。

ポイント内容
try には危険が起きるかもしれない処理を書く配列アクセス、数値変換、ファイル操作など
catch には例外が起きたときの対応を書くメッセージ表示、代替処理、記録など
例外が起きると try の残りは中断される例外発生行より後ろの try 内の文は実行されない
例外の種類と catch の種類が合う必要があるArrayIndexOutOfBoundsException には対応する catch が必要
catch の後は try-catch の外へ進む後続処理を続けられる

今回の Sample2.java では、try の中で power[10] = 9000; が実行された瞬間に例外が発生しました。

そのため、try 内の成功メッセージは表示されませんでした。
しかし、catch が例外を受け止めたため、最後の確認メッセージまでは進めました。

この流れが、try-catch の基本です。

ドラゴンボール風に try-catch を整理する

try-catch の考え方を、ドラゴンボール風に整理すると次のようになります。

Javaの考え方ドラゴンボール風のイメージ
try危険の可能性がある任務を進める場所
例外任務中に起きた想定外のトラブル
catch支援役がトラブルを受け止める場所
ArrayIndexOutOfBoundsException名簿の範囲外に記録しようとした警告
catch の後の処理状況確認後に任務報告へ進む

Sample2.java では、戦士本部の修行名簿に戦闘力を記録しようとしました。
しかし、5人分しかない名簿の10番目を指定したため、例外が発生しました。

その例外を catch が受け止め、

登録できる範囲をこえています。

と表示しました。

その後、プログラムは

修行記録の確認を終えました。

まで進みました。

このように、try-catch を使うと、問題が起きたときにプログラムがただ止まるのではなく、状況を受け止めて次の処理へ進めるようになります。

この内容で押さえておきたいこと

try-catch は、例外処理の入り口です。

最初から難しく考えすぎる必要はありません。
まずは、次の感覚をつかむことが大切です。

考え方内容
try危険が起きるかもしれない処理を入れる
catch実際に起きた例外を受け止める
例外発生後try の残りは中断される
catch が一致した場合catch の処理が実行される
catch の後try-catch の後ろへ進める

try-catch を使うと、プログラムは予想外のトラブルに少し強くなります。

もちろん、例外が起きた原因を直すことも大切です。
しかし、実行中にどうしても起こり得る問題に対して、あらかじめ対応を用意しておくことも重要です。

ドラゴンボール風に言えば、危険な任務そのものを避けるだけでなく、もしものときに支援役が受け止められる体制を作っておくことです。

Javaの try-catch は、そのための基本のしくみです。