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

予想外のトラブルも、try-catchがあれば落ち着いて受け止められる

プログラムを作っていると、思いどおりに処理が進む場面ばかりではありません。
実行してみたら、想定外の値が使われたり、使えない場所にアクセスしてしまったりして、そのままでは処理を続けられないことがあります。

こうした実行中のトラブルに対して、Javaでは例外処理というしくみを使います。
なかでも基本になるのが、try-catch です。

ドラゴンボールの世界で考えると、修行や戦いの最中に予想外の事態が起きても、何の備えもなければその場で流れが止まってしまいます。
ですが、あらかじめ対応役を決めておけば、トラブルが起きてもそこで状況を受け止めて、次の行動につなげることができます。

Javaの try-catch も、まさにそれと同じです。
危険が起こるかもしれない処理を try に入れ、もし本当に問題が起きたら catch で受け止めて対処します。
この考え方を身につけると、エラーでただ止まるだけのプログラムではなく、トラブルに対応できるプログラムへ一歩進めるようになります。

例外処理とは何か

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

前の段階では、配列の範囲をこえるような処理をしてしまうと、その場でプログラムが終了していました。
しかし、それでは使いやすいプログラムとは言えません。
そこで、問題が起きてもすぐに終わるのではなく、問題に応じたメッセージを出したり、その後の処理を続けたりするために、例外処理を行います。

ドラゴンボール風にたとえると、修行場の名簿に記録を書き込んでいるとき、存在しない欄に書こうとしてしまった場面です。
何も備えがなければ、その時点で記録作業は中断してしまいます。
でも、もし監視役がいて「その番号は使えません」と受け止めてくれれば、混乱せずに次へ進めます。

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

try と catch の役割

try-catch は、次の2つのブロックで成り立っています。

ブロック役割
try例外が起きるかもしれない処理を書く
catch実際に例外が起きたときの処理を書く

つまり、try は「まず処理をやってみる場所」、catch は「問題が起きたときに受け止める場所」です。

ドラゴンボールの世界でいえば、try は修行を実行している場面、catch は危険な事態が起きたときに界王様や仲間が状況を受け止めて立て直す場面に近いです。

サンプルプログラムで見てみよう

ここでは、配列の範囲をこえる例外を try-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("修行記録の確認を終えました。");
    }
}

このプログラムは何をしているのか

このプログラムでは、5人分の戦闘力を記録できる配列を用意しています。

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

この時点で使える添字は、0 から 4 までです。
ところが次の行では、10番目に値を入れようとしています。

power[10] = 9000;

これは配列の範囲外なので、通常なら ArrayIndexOutOfBoundsException という例外が発生します。
でも今回は、その処理を try ブロックの中に入れてあり、対応する catch ブロックも用意されています。

そのため、例外が起きてもプログラムはそこで完全には止まらず、catch ブロックでメッセージを出したあと、最後の処理まで進めます。

実行結果を見てみよう

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

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

ここで大切なのは、例外が起きたにもかかわらず、最後の

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

が実行されていることです。

これは、catch ブロックが例外を受け止めて処理したためです。
何も対処がなければ、途中で終了してこのメッセージは表示されません。

try-catch の流れを順番に整理する

try-catch の基本的な流れを順番に整理すると、次のようになります。

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

今回の Sample2.java に当てはめると、こうなります。

場面実際の動き
try の開始配列を作る
メッセージ表示修行名簿の10番目に戦闘力を登録します。
例外発生power[10] = 9000; で範囲外アクセス
catch に移動ArrayIndexOutOfBoundsException を受け取る
catch の処理登録できる範囲をこえています。 を表示
その後修行記録の確認を終えました。 を表示

途中の文が実行されない理由

try ブロックの中には、例外が起きる行のあとに次の文があります。

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

ですが、この文は実行されません。
なぜなら、その前の

power[10] = 9000;

のところで例外が発生し、try ブロックの処理がその時点で中断されるからです。

これはとても重要です。
try ブロックの中で例外が起きると、その後ろに書いてある文が自動で続くわけではありません
まず処理は止まり、対応する catch へ移動します。

ドラゴンボール風にいえば、修行記録を書き込んでいる途中で、存在しない欄に書こうとして警告が出たら、その場の記録作業はいったん止まり、管理係の対応に切り替わるイメージです。

catch の丸かっこの意味

catch の丸かっこの中には、次のように書かれています。

catch(ArrayIndexOutOfBoundsException e)

ここには2つの意味があります。

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

今回は、配列の範囲外アクセスが起きたときだけ、この catch ブロックで受け取るようにしています。
つまり、起きた例外の種類と catch に書かれている種類が一致したときにだけ、このブロックが動きます。

ドラゴンボールでたとえるなら、「修行中の転倒事故担当」「装置の故障担当」のように、担当するトラブルの種類が決まっている感じです。
担当外の問題なら、その catch では受け取れません。

なぜプログラムが最後まで進むのか

例外処理をしていない場合、例外が起きるとプログラムは途中で終了してしまいます。
ですが try-catch を使うと、問題が起きたときに catch ブロックで受け止められるので、必要な対応をしたうえで、その後ろの処理へ進めます。

今回なら、

  • try で危険な処理を実行する
  • 配列の範囲外アクセスで例外が起きる
  • catch でエラーメッセージを出す
  • 最後の確認メッセージまで進む

という流れです。

このしくみのおかげで、プログラムは「途中で突然止まるだけ」の状態から、「問題に対処しながら動ける」状態になります。
これが、エラーに強いプログラムへ近づく第一歩です。

図で流れを整理する

この図は、try ブロックで起きた例外が catch ブロックへ渡され、そのあとに後続の処理へ進むことを示しています。
コードだけだと見えにくい「処理の流れ」が、矢印で視覚的に理解できるのがポイントです。

この図から分かることは、主に次の3つです。

分かること内容
例外が起きる場所try の中で例外が発生する
例外の受け取り先種類が一致する catch が受け止める
その後の動きcatch の処理後、try-catch の外へ進める

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

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

これは、ただ表示を変えるだけではありません。
プログラムにとっては、「この問題にはここで対応する」という受け皿が用意されている状態です。

ドラゴンボール風にいえば、修行装置のエラー警報が鳴ったときに、担当の界王神がすぐに異常を確認し、「これは設定ミスだ」と判断して現場を立て直すようなものです。
受け皿があるからこそ、全体の流れを止めずに済みます。

catch ブロックがなかったらどうなるのか

もし Sample2.java に catch ブロックがなかったら、配列の範囲外アクセスが起きた時点で、main メソッドの処理はそこで終わってしまいます。
そして、最後のメッセージも表示されません。

main メソッドは、プログラムの開始地点です。
ここで例外が起きて、それを受け止める catch が見つからなければ、それ以上戻る場所がないため、プログラムは終了します。

この点からも、catch ブロックが「ただのおまけ」ではなく、例外を処理するための重要な受け皿であることが分かります。

try-catch を使う意味

try-catch を使う意味は、とてもシンプルです。
問題が起きても、そこで終わりにしないためです。

今回の例では、配列の範囲をこえるというミスがありました。
それでも try-catch を使ったことで、

  • 何が起きたのかを利用者に伝えられる
  • プログラムが途中で突然終わらない
  • 必要なら後続の処理へ進める

という利点が生まれます。

つまり try-catch は、プログラムにとっての安全装置のようなものです。
ドラゴンボールの戦いでいえば、強い敵が現れても即座に全滅するのではなく、仲間が受け止めて立て直すための体制にあたります。

この段階でしっかり押さえたいポイント

最後に、この内容で特に大事な点を整理しておきます。

ポイント内容
try の役割例外が起きるかもしれない処理を書く
catch の役割起きた例外を受け止めて処理する
try の中で例外が起きたらその場で try の後続処理は中断される
catch が一致したらcatch の処理が実行される
catch のあとtry-catch の後ろの処理へ進める

この流れをしっかりつかめると、次に学ぶ複数の catch や finally なども理解しやすくなります。

try-catch は、例外処理の入口です。
まずは今回のように、危険がありそうな処理を try に入れ、問題が起きたら catch で受け止めるという基本の形を、しっかり自分の中に定着させることが大切です。