Java入門|例外のしくみと基本

予想外のトラブルも、例外のしくみを知れば落ち着いて対処できる

プログラムは、書いた通りに動いてくれるのが理想です。ですが実際には、実行してはじめて問題が表面に出ることがあります。
たとえば、想定していない値が入ってきたり、使えるはずの場所にアクセスできなかったり、数の範囲をこえてしまったりする場面です。

Javaには、そのような実行中のトラブルに対応するためのしくみとして、例外があります。
このしくみを理解すると、ただ「エラーで止まった」で終わるのではなく、どこで、どんな問題が起きたのかを読み取れるようになります。

ここでは、例外とは何か、なぜ起きるのか、そして実行中に何が起きているのかを、ドラゴンボールの世界観にたとえながらやさしく整理していきます。
例外は少しむずかしそうに見えますが、考え方はそこまで複雑ではありません。
「想定外の出来事が発生したときに、Javaが知らせてくれる合図」と考えると、イメージしやすくなります。

例外とは何か

例外は、プログラムの実行中に起こる異常な出来事を表すしくみです。

ドラゴンボールの世界で考えてみましょう。
たとえば悟空が、修行場にあるはずの仙豆を取りに行ったとします。ところが、その棚には仙豆がありませんでした。
あるいは、スカウターで戦闘力を数値として読み取るはずなのに、文字のような意味不明なデータが流れ込んできたとします。
また、5人分しか並べられない戦士名簿に、10番目の場所へ無理やり書き込もうとしたとします。

こうした「進めようとしたけれど、そのままでは処理できない」という状況が、Javaでいう例外のイメージです。

日常のプログラムでも、次のような場面で例外が起こります。

場面何が起きているか例外のイメージ
ファイルを開こうとした指定したファイルが存在しない必要な道具が見つからない
文字列を数値に変換しようとした数字ではない文字が入っていたスカウターが正しい数値を読めない
配列の決められた範囲をこえて使った用意された場所より先にアクセスした戦士名簿の欄外に書こうとした

ここで大事なのは、これらの問題の多くはコンパイル時には見つからないということです。
Javaの文法として正しく書けていても、実際に動かしたときの値や状況によって問題が起こる場合があります。

つまり例外は、実行してはじめて分かるトラブルを知らせるしくみです。

コンパイルエラーとは何が違うのか

例外を理解するためには、コンパイルエラーとの違いをおさえると分かりやすいです。

種類いつ分かるか
コンパイルエラー実行前文法ミス、セミコロン忘れ、型の不一致
例外実行中配列の範囲外アクセス、数値変換の失敗など

コンパイルエラーは、出発前の点検で見つかる不具合のようなものです。
一方で例外は、実際に飛び立ってみたら乱気流に巻き込まれた、というようなものです。

ドラゴンボール風にいえば、
コンパイルエラーは「技の型が最初から間違っていて発動すらできない状態」、
例外は「技は発動したけれど、戦闘中の状況が想定外で処理が止まる状態」と考えるとよいでしょう。

例外が起きる流れをつかもう

例外は、何の前触れもなく突然現れるように見えるかもしれません。
ですが、内部ではある程度はっきりした流れがあります。

  1. プログラムが順番に処理を進める
  2. 実行中に不正な状況に出会う
  3. Javaがその問題を例外として知らせる
  4. 対応がなければ、プログラムはその場で終了する

この「Javaが問題を例外として知らせる」ことを、例外が送出されるといいます。
英語では throw と表現されます。

たとえば、悟空が瞬間移動で向かう座標を受け取ったとき、存在しない場所が指定されていたら、そのまま処理を続けるわけにはいきません。
そこで「その座標は不正です」と強い警告が出るイメージです。
これが例外の送出です。

配列の範囲外アクセスを例に見てみよう

例外の基本をつかむために、配列を使ったシンプルな例を見てみます。
ここでは、もとの流れを活かしつつ、ドラゴンボールの世界観に合わせた内容に置き換えています。

今回は、5人分の戦士データを入れるための配列を用意しているのに、存在しない10番目の場所に値を入れようとする例です。

ファイル名:Sample1.java

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

        System.out.println("戦士リストの10番目に戦闘力を記録します。");

        power[10] = 9000;

        System.out.println("10番目に戦闘力9000を記録しました。");
        System.out.println("修行データの登録が完了しました。");
    }
}

このプログラムでは、配列 power の要素数は 5 です。
つまり使えるのは、0番目から4番目までです。

ところが、次の行では 10番目 に値を代入しようとしています。

power[10] = 9000;

これは、用意された箱の数をこえて、存在しない場所に値を入れようとしている状態です。
そのため、実行すると途中で例外が発生します。

実行すると何が起きるのか

このプログラムを実行すると、最初のメッセージは表示されます。
しかし、その次に配列の範囲外アクセスが起きるため、処理が止まります。

実行結果のイメージは次のようになります。

戦士リストの10番目に戦闘力を記録します。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
    at Sample1.main(Sample1.java:10)

ここで注目したいポイントは3つあります。

表示内容意味
Exception in thread "main"mainメソッドの実行中に例外が起きた
java.lang.ArrayIndexOutOfBoundsException配列の範囲外にアクセスした
at Sample1.main(Sample1.java:10)Sample1.java の 10行目あたりで問題が起きた

つまり、Javaはただ止まるだけではなく、どんな種類の問題が、どこで起きたのかを教えてくれています。

なぜ後ろのメッセージは表示されないのか

プログラムには、次のメッセージも書かれています。

System.out.println("10番目に戦闘力9000を記録しました。");
System.out.println("修行データの登録が完了しました。");

ですが、これらは表示されません。
なぜなら、その前の行で例外が起きてしまい、そこで通常の処理の流れが止まるからです。

ドラゴンボール風にいえば、修行記録を神殿の台帳に書き込んでいた途中で、存在しないページ番号を指定してしまったようなものです。
その瞬間に管理役が「そのページはありません」と止めるので、その先の記録処理は続けられません。

例外が起きると、何も対策していない場合は、その場でプログラム全体が終了してしまいます。
この点は、初心者のうちにしっかり意識しておくととても大切です。

ArrayIndexOutOfBoundsException とは何か

今回起きた例外の名前は ArrayIndexOutOfBoundsException です。
長い名前ですが、意味を分けると分かりやすいです。

部分意味
Array配列
Index添字、番号
OutOfBounds範囲外
Exception例外

つまり、配列の添字が範囲外ですという意味です。

今回の配列は 5個分の要素しかないので、使える添字は 0 ~ 4 です。
それなのに 10 を指定したため、この例外が発生しました。

ここで覚えておきたいのは、配列の「要素数」と「添字」は同じではない、ということです。

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

配列は 5個あるから 5番目まで使える、と感覚的に思いやすいのですが、Javaの添字は 0 から始まります。
このずれが、配列の例外のよくある原因です。

例外が送出されるとはどういうことか

Javaでは、例外が起きることを
例外が送出された
と表現します。

これは、プログラムの中で「このままでは処理を続けられません」という情報が投げられた、と考えるとイメージしやすいです。

ドラゴンボールのたとえでいえば、界王様のもとで修行中に、重力装置の設定値が危険な範囲に入った瞬間、警報が発せられるようなものです。
その警報が、例外です。

つまり例外は、単なる失敗ではなく、異常事態をプログラムに知らせるための仕組み化された信号です。

この例から分かること

今回の Sample1.java からは、例外について次のことが分かります。

分かること内容
例外は実行中に起きる文法が正しくても、動かしてはじめて分かる問題がある
問題の種類が表示される今回は配列の範囲外アクセス
問題の場所が表示されるどのファイルのどの位置かをたどれる
例外後は通常処理が止まる後ろに書いた文は実行されない

これは、例外が怖いものというより、問題を特定しやすくするための手がかりでもあるということです。
エラーメッセージは難しそうに見えますが、読み方を知るとかなり心強い情報源になります。

ドラゴンボールの世界で考える例外のイメージ

ここで、例外の考え方をドラゴンボールの世界で整理してみましょう。

ドラゴンボールの場面Javaでの考え方
5人分の修行台帳しかないのに10人目を書こうとする配列の範囲外アクセス
スカウターに数値ではない情報が入る数値変換の失敗
取りに行った仙豆が保管庫にないファイルやデータが見つからない
危険な設定が見つかり警報が出る例外の送出

このように考えると、例外は「Javaが勝手に怒っている」のではなく、
無理な処理や危険な処理を見つけて、きちんと知らせてくれているのだと分かります。

図で流れを整理する

図にすると、今回の動きはとても分かりやすくなります。
教材用の図を作るなら、次のような内容が向いています。

この図は、配列の使える範囲をこえた瞬間に例外が発生することを表しています。
とくに、配列そのものは 5個分しかないのに、10番目へ代入しようとしている点を視覚的に確認できるのがポイントです。

この図から分かるのは、次の2点です。

  • 例外は何となく起きるのではなく、不正な操作がきっかけで発生する
  • 例外名を見ることで、どんな種類の問題なのかを判断できる

コードだけだと見落としやすい「配列の範囲」と「実際に使おうとした場所」のズレが、図にするとすぐ見えてきます。

例外は悪者ではなく、学習の手がかりになる

はじめて例外を見ると、英語の長い名前が並んでいて身構えてしまいやすいです。
ですが、例外はプログラム学習の敵というより、何がまずかったのかを教えてくれる案内役です。

たとえば今回なら、
Javaは「配列の外に出ています」とかなりはっきり教えてくれています。
そのおかげで、配列の添字を見直せばよいと判断できます。

初心者のうちは、例外が出たら落ち込むのではなく、次の順番で確認するとよいです。

確認すること見るポイント
例外名何が原因かの大まかな種類
行番号どこで起きたか
直前のコード何をしようとしていたか
値や範囲配列の長さ、入力値、番号など

この見方に慣れていくと、例外メッセージがかなり読めるようになります。

これから先につながる土台

ここで学んだのは、例外のもっとも基本的な考え方です。
つまり、

  • 実行中の問題は例外として表れる
  • 例外には種類がある
  • 例外が起きると処理が止まることがある
  • 例外メッセージには原因と場所のヒントがある

ということです。

この土台を理解しておくと、この先で学ぶ例外処理の文法も入りやすくなります。
「なぜ try や catch が必要なのか」も、自然につながって見えてきます。

例外は、ドラゴンボールの戦いでいえば、予想外の乱入や装置トラブルのようなものです。
何も準備していなければ、その場で流れが止まってしまいます。
でも、仕組みを知っていれば、落ち着いて状況を見て対処できるようになります。

Javaの例外も同じです。
まずは今回のように、どんなときに起きるのかをしっかりつかむことが第一歩です。