Java超|配列の範囲外アクセスと例外

予想外のトラブルも、例外の合図を読めば落ち着いて対処できる。Javaの例外は、危険な処理を知らせてくれる戦士本部の警報です。

Javaのプログラムは、書いた通りに正しく動いてくれるのが理想です。

しかし、実際にプログラムを動かしてみると、思いがけない問題が起きることがあります。

たとえば、用意していない場所にアクセスしようとしたり、数字として扱うつもりだった値が数字ではなかったり、存在するはずのファイルやデータが見つからなかったりする場合です。

Javaには、このような 実行中のトラブル を知らせるしくみがあります。
それが 例外 です。

例外は、プログラムが「このままでは処理を続けられません」と知らせてくれる合図です。
ただ止まるだけではなく、どんな種類の問題が、どのあたりで起きたのかを教えてくれます。

ドラゴンボール風にたとえると、悟空たちが任務中に、想定外の危険に出会ったようなものです。

任務中の出来事Javaでのイメージ
5人分しかない戦士名簿の10番目に記録しようとする配列の範囲外にアクセスする
戦闘力として読むはずの札に文字が書かれている数値変換に失敗する
カプセル保管庫にあるはずの道具記録が見つからないファイルやデータが見つからない
危険な任務指令が出て警報が鳴る例外が発生する

例外は、プログラムの失敗をただ知らせるだけのものではありません。
どこで何が起きたのかを知るための重要な手がかりでもあります。

この記事では、例外とは何か、コンパイルエラーと何が違うのか、配列の範囲外アクセスでは何が起きるのかを、ドラゴンボールの世界観に置きかえてやさしく整理していきます。

例外とは何か

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

Javaの文法としては正しく書けていても、実際に動かしたときの値や状況によって、処理を続けられないことがあります。

ドラゴンボール風に考えてみましょう。

悟空たちの戦士本部には、戦闘力を記録する名簿があります。
その名簿には、5人分の欄しかありません。

ところが、任務管理プログラムが、存在しない10番目の欄に戦闘力を記録しようとしました。

このとき、戦士本部の管理システムは「その欄は存在しません」と警告を出します。
存在しない場所へ無理に書き込むことはできないからです。

Javaでも同じです。

配列に5個分の場所しか用意していないのに、10番目へアクセスしようとすると、Javaは「その番号は範囲外です」と知らせます。
これが例外です。

Javaで起きることドラゴンボール風のイメージ
配列の範囲外にアクセスする戦士名簿の存在しない欄に記録しようとする
数値変換に失敗する戦闘力札に数字ではない文字が書かれている
ファイルが見つからないカプセル保管庫にあるはずの巻物がない
例外が発生する戦士本部の警報システムが処理を止める

ここで大切なのは、例外は 実行してはじめて分かるトラブル だという点です。

プログラムの文法が正しくても、実際の処理内容によって問題が起こることがあります。

コンパイルエラーと例外の違い

例外を理解するときは、コンパイルエラーとの違いを押さえると分かりやすくなります。

コンパイルエラーは、実行前に見つかる問題です。
たとえば、セミコロンを忘れた、変数名を間違えた、型が合わない、というような文法上の問題です。

一方、例外は、実行中に起こる問題です。
文法は正しくても、実際に動かしたときに不正な値や不正な操作が出てくると発生します。

種類いつ分かるかドラゴンボール風のイメージ
コンパイルエラー実行前文法ミス、型の不一致、セミコロン忘れかめはめ波の構えが間違っていて技を出せない
例外実行中配列の範囲外アクセス、数値変換失敗技は出たが、任務中に想定外の危険が起きる

ドラゴンボール風に言えば、コンパイルエラーは出発前の点検で見つかる不備です。
戦闘服の装着手順が間違っている、スカウターの設定式が壊れている、指令書の形式が崩れているような状態です。

例外は、任務に出てから起こるトラブルです。
道が崩れていた、カプセル保管庫が空だった、名簿の存在しない欄を参照してしまった、というような状況です。

どちらも問題ではありますが、見つかるタイミングが違います。

例外が起きる流れ

例外は突然出てくるように見えますが、実行中にはある程度はっきりした流れがあります。

順番起きること
1プログラムが上から順番に処理を進める
2実行中に不正な状況に出会う
3Javaがその問題を例外として知らせる
4対応がなければ、その場で通常処理が止まる

Javaでは、例外が発生することを 例外が送出される といいます。

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

ドラゴンボール風にたとえると、戦闘中にスカウターが危険な異常値を検知して、戦士本部へ警告を飛ばすようなものです。

「その場所には進めません」
「その記録欄は存在しません」
「その値は扱えません」

このような警告が、Javaでは例外として表れます。

図:例外が発生する基本の流れ

この図が示していること

この図は、例外が発生するまでの流れを表しています。

最初は、プログラムが上から順番に処理を進めます。
その途中で、存在しない場所にアクセスするような不正な状況に出会うと、Javaは例外を発生させます。

対応が用意されていない場合、その場所で通常の処理は止まります。

図の流れ意味
処理を進めるプログラムが通常通り動いている
不正な状況処理を続けられない問題に出会う
例外が発生Javaが問題を知らせる
対応がなければ停止後続の処理が実行されない

この図から分かることは、例外は何となく起きるものではなく、実行中の不正な操作をきっかけに発生するということです。

配列の範囲外アクセスで例外を見てみる

ここでは、例外の基本をつかむために、配列を使ったシンプルな例を見てみます。

今回のプログラムでは、5人分の戦士の戦闘力を記録するために、int 型の配列 power を用意します。

配列の要素数は5です。
つまり、使える番号は 0番目から4番目までです。

ところが、プログラムでは 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 が持っている場所

このプログラムでは、次のように配列を作成しています。

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

new int[5] は、int 型の値を5個入れられる配列を作るという意味です。

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

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

つまり、power 配列で使えるのは次の範囲です。

添字使えるか
power[0]使える
power[1]使える
power[2]使える
power[3]使える
power[4]使える
power[5]使えない
power[10]使えない

今回の問題は、次の行です。

power[10] = 9000;

power[10] は、5個分しかない配列の範囲を大きく超えています。

ドラゴンボール風にたとえると、5人分の戦士名簿しかないのに、10番目の欄へ戦闘力を書こうとしている状態です。

存在しない欄には記録できません。
そのため、Javaは例外を発生させます。

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

このプログラムを実行すると、最初のメッセージは表示されます。

戦士リストの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)

ここで表示されている内容は、怖い文字列のように見えるかもしれません。
しかし、読み方が分かれば、とても役に立つ情報です。

表示内容意味
Exception in thread "main"main メソッドの実行中に例外が起きた
java.lang.ArrayIndexOutOfBoundsException配列の範囲外にアクセスした
Index 10 out of bounds for length 5長さ5の配列に対して添字10を使った
at Sample1.main(Sample1.java:10)Sample1.java の10行目あたりで問題が起きた

Javaは、ただ「止まりました」とだけ言っているわけではありません。

どんな種類の問題なのか。
どの番号が不正だったのか。
配列の長さはいくつだったのか。
どのファイルのどの行で起きたのか。

このような情報を教えてくれています。

後ろのメッセージが表示されない理由

Sample1.java には、次のメッセージも書かれています。

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

しかし、実行結果には表示されません。

理由は、その前の行で例外が起きているからです。

power[10] = 9000;

対応が用意されていない状態で例外が発生すると、通常の処理はそこで止まります。
そのため、その後ろに書かれている文は実行されません。

ドラゴンボール風にたとえると、戦士名簿に記録している途中で、存在しない欄を指定してしまったようなものです。

管理システムが「その欄はありません」と止めるため、
「記録しました」
「登録が完了しました」
という次の報告には進めません。

処理実行されるか
最初のメッセージ表示実行される
power[10] = 9000;ここで例外が起きる
10番目に戦闘力9000を記録しました。実行されない
修行記録の登録が完了しました。実行されない

例外が発生した位置より後ろの通常処理は、何も対策していない場合は進まない。
ここはとても大切なポイントです。

図:配列の範囲外アクセスで例外が起きる

この図が示していること

この図は、配列 power の使える範囲と、実際にアクセスしようとした場所のズレを表しています。

power = new int[5] によって、配列には5個分の場所が用意されます。
ただし、添字は 0 から始まるため、使えるのは power[0] から power[4] までです。

power[10] は、その範囲の外にあります。

要素意味
power[0] 〜 power[4]実際に存在する配列の場所
power[10]存在しない場所
ArrayIndexOutOfBoundsException配列の範囲外アクセスを知らせる例外

この図から分かることは、例外が偶然起きるのではなく、存在しない場所にアクセスしようとしたことが原因で発生するということです。

ArrayIndexOutOfBoundsException の意味

今回発生した例外は、ArrayIndexOutOfBoundsException です。

長い名前ですが、分けて見ると意味が分かりやすくなります。

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

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

今回の配列は、5個分の要素を持っています。

power = new int[5];

そのため、使える添字は 0, 1, 2, 3, 4 です。

しかし、実際には 10 を指定しました。

power[10] = 9000;

そのため、Javaは ArrayIndexOutOfBoundsException を発生させました。

配列の長さ使える添字指定した添字結果
50〜410範囲外なので例外

初心者がつまずきやすいのは、配列の要素数と添字を混同するところです。

5個分の配列だから 5番目まで使える、と思ってしまうことがあります。
しかし、Javaの配列は 0 から数えるため、5個分なら最後は 4番目です。

感覚的な数え方Javaの添字
1個目0
2個目1
3個目2
4個目3
5個目4

ドラゴンボール風にたとえると、戦士名簿には5つの欄があります。
ただし、Javaの名簿では欄番号が 0 から始まるため、最後の欄は 4番です。

例外メッセージは原因を探す手がかり

例外メッセージは、最初は難しく見えます。

しかし、見るべきポイントを決めておくと、かなり読みやすくなります。

今回の実行結果をもう一度見てみます。

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
    at Sample1.main(Sample1.java:10)

この中で、特に見るべきポイントは次の3つです。

確認する場所分かること
例外名どんな種類の問題か
詳細メッセージどの値が不正だったか
at から始まる行どのファイルのどの行で起きたか

今回なら、次のように読み取れます。

情報読み取れること
ArrayIndexOutOfBoundsException配列の範囲外アクセス
Index 10 out of bounds for length 5長さ5の配列に対して10を指定した
Sample1.java:10Sample1.java の10行目あたりで起きた

つまり、例外メッセージは、ただのエラー表示ではありません。

原因を探すための地図のようなものです。

ドラゴンボール風に言えば、戦士本部の警報システムが「どの任務で、どの地点で、どんな異常が起きたか」を知らせてくれている状態です。

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

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

送出という言葉は少し硬いですが、イメージとしては「異常事態の情報が投げられる」ということです。

プログラムが処理を進めている途中で、続行できない問題に出会ったとき、Javaは例外オブジェクトという形で問題を知らせます。

ここでは、詳しい例外処理の文法までは扱いません。
まずは、例外が「実行中の異常を知らせる信号」として発生することを押さえれば大丈夫です。

ドラゴンボール風にたとえると、危険な惑星での任務中に、スカウターが異常な戦闘力反応を検知して警報札を飛ばすようなものです。

その警報札には、次のような情報が含まれます。

警報札の情報Javaの例外情報
どんな危険か例外名
どこで起きたかファイル名と行番号
何が原因か詳細メッセージ
その後どうなるか対応がなければ処理停止

例外は単なる失敗ではなく、問題を伝えるための仕組み化された信号です。

例外は怖いものではなく学習の手がかり

はじめて例外を見ると、英語の長い名前や複数行のメッセージに驚くかもしれません。

しかし、例外は学習の敵ではありません。

むしろ、何がまずかったのかを教えてくれる案内役です。

今回なら、Javaは次のことを教えてくれています。

Javaが教えてくれていること内容
問題の種類配列の範囲外アクセス
不正な値添字 10
配列の長さ5
問題の場所Sample1.java の10行目あたり

これだけ分かれば、確認する場所はかなり絞れます。

初心者のうちは、例外が出たら次の順番で見ると分かりやすいです。

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

例外が出たら、まず落ち着いて例外名と行番号を見る。
この習慣がつくと、エラー対応がぐっと楽になります。

図:例外メッセージの読み方

この図が示していること

この図は、例外メッセージのどこを読めばよいのかを表しています。

例外メッセージには、主に次の情報が含まれます。

見る場所分かること
Exception in thread "main"main の実行中に起きた
ArrayIndexOutOfBoundsException配列の範囲外アクセス
Index 10 out of bounds for length 5長さ5の配列に10を指定した
Sample1.java:10問題が起きた場所

この図から分かることは、例外メッセージはただの警告文ではなく、原因をたどるための地図だということです。

ドラゴンボール風に例外を整理する

例外の考え方を、ドラゴンボール風にもう一度整理してみましょう。

ドラゴンボール風の場面Javaでの考え方
5人分の戦士名簿しかないのに10番目へ記録する配列の範囲外アクセス
戦闘力の札として読むはずが文字札だった数値変換の失敗
任務用のカプセル記録が保管庫にないファイルやデータが見つからない
危険を知らせるスカウター警報が飛ぶ例外が送出される
警報に対応しなければ任務が止まる例外処理がないとプログラムが停止する

このように考えると、例外はJavaが勝手に怒っているわけではありません。

むしろ、危険な処理や不正な状況を見つけて、きちんと知らせてくれている状態です。

例外が出たら、まず次のように考えるとよいです。

考え方内容
何が起きたのか例外名を見る
どこで起きたのかファイル名と行番号を見る
なぜ起きたのか詳細メッセージと直前のコードを見る
どの値が問題か添字、入力値、配列の長さなどを見る

今回の Sample1.java では、power[10] が問題でした。

配列の長さは5なので、使えるのは power[0] から power[4] までです。
この範囲を超えたため、ArrayIndexOutOfBoundsException が発生しました。

これからの例外処理につながる考え方

ここで学んだのは、例外の基本です。

まず押さえておきたいことは、次の4つです。

基本内容
例外は実行中に起きる文法が正しくても、動かして初めて分かる問題がある
例外には種類があるArrayIndexOutOfBoundsException など
例外が起きると処理が止まることがある対応がない場合、後続の通常処理は進まない
例外メッセージには手がかりがある原因、場所、問題の値を確認できる

この考え方が分かると、この先で学ぶ try や catch の意味も理解しやすくなります。

try や catch は、例外が起きそうな処理に備えて、プログラムがその場で完全に止まらないようにするためのしくみです。

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

何も準備していなければ、想定外の事態で任務が止まります。
しかし、事前に対応方法を決めておけば、状況を確認して別の処理へ進める可能性があります。

今回の段階では、まず次の感覚をつかむことが大切です。

例外は、実行中の異常を知らせる合図であり、原因を探すための手がかりでもある。

この感覚があると、例外メッセージを見たときに、ただ驚くのではなく、落ち着いて読み解けるようになります。