- JAVA
Java ArrayIndexOutOfBoundsException原因と対処
ArrayIndexOutOfBoundsExceptionとは、配列に存在しない番号(インデックス)で要素を読み書きしたときに発生する例外です。 長さ 3 の配列で使える番号は 0・1・2 の3つで、3 や -1 を指定するとこの例外になります。
メッセージの Index 3 out of bounds for length 3 は、「番号 3 を使ったが、配列の長さは 3 だった」という意味です。この記事では、メッセージとスタックトレースから原因の行を特定する手順を図で示し、よくある6つの原因を再現コードと修正コードで並べました。コードはすべて Java 11.0.32.1・17.0.20.1・21.0.10・25.0.4.1 で動かし、メッセージの違いは Java 8(1.8.0_504)でも確かめています。
ArrayIndexOutOfBoundsExceptionの原因と対処の早見表
| 原因 | エラーになる書き方 | 表示されるメッセージ | 直し方 |
|---|---|---|---|
ループ条件が <= |
for (i = 0; i <= a.length; i++) |
Index 3 out of bounds for length 3 |
条件を i < a.length にする |
| 空配列の先頭を読む | empty[0] |
Index 0 out of bounds for length 0 |
a.length > 0 を先に確かめる |
引数なしで args[0] |
java Main で起動 |
Index 0 out of bounds for length 0 |
args.length を確かめ、使い方を表示 |
| 多次元配列の行と列の取り違え | 2行3列で table[2][0] |
Index 2 out of bounds for length 2 |
行は table.length、列は table[row].length |
| 負のインデックス | a[-1]、a[indexOf の結果] |
Index -1 out of bounds for length 3 |
-1(見つからない)を先に分ける |
| length - 1 の計算ずれ | a[a.length]、a[i + 1] |
Index 3 out of bounds for length 3 |
末尾は a[a.length - 1]、ループは length - 1 まで |
メッセージの Index の数字と length の数字を見比べると、どの原因かを絞り込めます。
| Index と length の関係 | 考えられる原因 |
|---|---|
| Index と length が同じ(3 と 3) | <= のループ、a[a.length]、a[i + 1] |
| length が 0 | 空配列、引数なしの args、split の結果が空 |
| Index が -1 などの負数 | indexOf の結果、length - 1 を空配列で計算、負数の % |
| Index が length より大きく離れている | 別の配列の長さや、行と列を取り違えた番号を使っている |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java ArrayIndexOutOfBoundsException原因と対処」
エラーメッセージとスタックトレースの読み方
まず、最小の再現コードです。
public class Trace {
public static void main(String[] args) {
int[] scores = {70, 85, 90};
printAll(scores);
}
static void printAll(int[] scores) {
for (int i = 0; i <= scores.length; i++) {
System.out.println(i + "番目: " + scores[i]);
}
}
}
実行結果(Java 21.0.10、java Trace.java。11.0.32.1・17.0.20.1・25.0.4.1 も同じ表示):
0番目: 70
1番目: 85
2番目: 90
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Trace.printAll(Trace.java:9)
at Trace.main(Trace.java:4)
1行目とatの行に書いてあること
上の表示を、場所ごとに分けて読みます(図)。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~
(1) 例外の種類 (2) 使った番号 (3) 配列の長さ
at Trace.printAll(Trace.java:9) <- (4) 発生した場所。Trace.java の 9 行目
at Trace.main(Trace.java:4) <- (5) それを呼び出した場所。4 行目
| 番号 | 読み取れること |
|---|---|
| (1) | 配列の範囲外を読み書きした |
| (2) | 使おうとした番号は 3 |
| (3) | 配列の長さは 3。使える番号は 0〜2 |
| (4) | Trace.java の 9 行目、scores[i] で発生 |
| (5) | 4 行目の printAll(scores) から呼ばれた |
行を特定する手順
flowchart TD
A["1行目で Index と length の数字を読む"] --> B["at の行を上から見て、最初に出てくる自分のクラスを探す"]
B --> C["かっこ内の ファイル名:行番号 を開く"]
C --> D["その行で配列に [ ] を使っている箇所を探す"]
D --> E["[ ] の中の変数が Index の値になる理由を調べる"]
E --> F{"値はその行で決まっている?"}
F -- はい --> G["ループ条件や計算式を直す"]
F -- いいえ --> H["1つ下の at の行(呼び出し元)へさかのぼる"]
今回の例では、9 行目の scores[i] で i が 3 になっています。i を決めているのは 8 行目の i <= scores.length なので、ここを i < scores.length に直せば解決します。例外の直前まで「0番目」「1番目」「2番目」が表示されていることも手がかりで、3回目までは正常に動き、4回目で範囲を超えたと読み取れます。
1行に [ ] が複数あって迷うときは、式を変数に分けて行を分割すると、行番号で絞り込めます。
Java 8 以前はメッセージが数字だけ
同じコードを Java 8(1.8.0_504)で動かすと、次のように表示されました。
0番目: 70
1番目: 85
2番目: 90
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at Trace.printAll(Trace.java:9)
at Trace.main(Trace.java:4)
コロンの後ろの 3 は「使おうとした番号」です。配列の長さは表示されないので、行番号から配列を特定して長さを自分で確かめます。Java 11.0.32.1 では Index 3 out of bounds for length 3 の形でした(Java 9・10 は未実行)。
length・length()・size()と例外名の対応表
配列・文字列・リストは、長さの調べ方も、範囲外のときの例外名も違います。
| 対象 | 長さの調べ方 | 使える番号 | 範囲外のときの例外 |
|---|---|---|---|
配列 int[] a |
a.length(かっこなし) |
0 〜 a.length - 1 |
ArrayIndexOutOfBoundsException |
文字列 String s |
s.length() |
0 〜 s.length() - 1 |
StringIndexOutOfBoundsException |
ArrayList などの List |
list.size() |
0 〜 list.size() - 1 |
IndexOutOfBoundsException |
Arrays.asList(...) の List |
list.size() |
0 〜 list.size() - 1 |
ArrayIndexOutOfBoundsException |
StringBuilder |
sb.length() |
0 〜 sb.length() - 1 |
StringIndexOutOfBoundsException |
3つの例外は親子関係にあります。ArrayIndexOutOfBoundsException と StringIndexOutOfBoundsException は、どちらも IndexOutOfBoundsException の子クラスで、その親は RuntimeException です(後述の Fixed.java で親クラスを表示して確認)。そのため catch (IndexOutOfBoundsException e) と書けば3つとも受けられます。
実際に発生させて、例外名とメッセージを確かめたコードです。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Messages {
interface Action { Object run(); }
static void show(String label, Action a) {
try {
System.out.println(label + " => " + a.run());
} catch (RuntimeException e) {
System.out.println(label + " => " + e.getClass().getName() + ": " + e.getMessage());
}
}
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
final int[] a = {10, 20, 30};
final String s = "abc";
final List<String> list = new ArrayList<>(Arrays.asList("x", "y", "z"));
final List<String> fixed = Arrays.asList("x", "y", "z");
show("a[3]", () -> a[3]);
show("a[-1]", () -> a[-1]);
show("s.charAt(3)", () -> s.charAt(3));
show("s.substring(4)", () -> s.substring(4));
show("s.substring(2, 5)", () -> s.substring(2, 5));
show("list.get(3)", () -> list.get(3));
show("list.get(-1)", () -> list.get(-1));
show("new ArrayList<>().get(0)", () -> new ArrayList<String>().get(0));
show("Arrays.asList(...).get(3)", () -> fixed.get(3));
show("list.remove(3)", () -> list.remove(3));
show("list.add(5, \"w\")", () -> { list.add(5, "w"); return "ok"; });
show("System.arraycopy(a, 0, new int[2], 0, 3)", () -> { System.arraycopy(a, 0, new int[2], 0, 3); return "ok"; });
show("Arrays.copyOfRange(a, 4, 5)", () -> Arrays.copyOfRange(a, 4, 5));
show("new StringBuilder(\"abc\").charAt(3)", () -> new StringBuilder("abc").charAt(3));
show("new int[-1]", () -> new int[-1]);
}
}
実行結果(Java 21.0.10。25.0.4.1 も1行目以外同じ):
java.version = 21.0.10
a[3] => java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
a[-1] => java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
s.charAt(3) => java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
s.substring(4) => java.lang.StringIndexOutOfBoundsException: Range [4, 3) out of bounds for length 3
s.substring(2, 5) => java.lang.StringIndexOutOfBoundsException: Range [2, 5) out of bounds for length 3
list.get(3) => java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
list.get(-1) => java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 3
new ArrayList<>().get(0) => java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
Arrays.asList(...).get(3) => java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
list.remove(3) => java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
list.add(5, "w") => java.lang.IndexOutOfBoundsException: Index: 5, Size: 3
System.arraycopy(a, 0, new int[2], 0, 3) => java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 3 out of bounds for int[2]
Arrays.copyOfRange(a, 4, 5) => java.lang.ArrayIndexOutOfBoundsException: arraycopy: length -1 is negative
new StringBuilder("abc").charAt(3) => java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
new int[-1] => java.lang.NegativeArraySizeException: -1
Arrays.asList で作ったリストは内部で配列をそのまま使っているため、get(3) の例外名が配列と同じになります。また、配列の長さに負数を指定したときは別の例外(NegativeArraySizeException)です。
バージョンによるメッセージの違い
同じ Messages.java を5つの版で動かした結果です。例外名はどの版でも同じで、メッセージの文面が変わります。
| 操作 | Java 8 | Java 11 | Java 17 | Java 21・25 |
|---|---|---|---|---|
a[3] |
3 |
Index 3 out of bounds for length 3 |
同左 | 同左 |
s.charAt(3) |
String index out of range: 3 |
String index out of range: 3 |
同左 | Index 3 out of bounds for length 3 |
s.substring(2, 5) |
String index out of range: 5 |
begin 2, end 5, length 3 |
同左 | Range [2, 5) out of bounds for length 3 |
list.get(3) |
Index: 3, Size: 3 |
Index 3 out of bounds for length 3 |
同左 | 同左 |
list.get(-1) |
例外名が ArrayIndexOutOfBoundsException、メッセージ -1 |
IndexOutOfBoundsException Index -1 out of bounds for length 3 |
同左 | 同左 |
System.arraycopy の範囲外 |
メッセージ null |
arraycopy: last destination index 3 out of bounds for int[2] |
同左 | 同左 |
検索するときは例外名と行番号を手がかりにすると、版による文面の違いに左右されません。
原因別の再現コード
6つの原因を、1つのファイルで順に発生させます。例外を catch してメッセージを表示しているので、最後まで実行されます(java Causes.java を引数なしで実行)。
import java.util.Objects;
public class Causes {
interface Action { Object run(); }
static void show(String label, Action a) {
try {
System.out.println(label + " => " + a.run());
} catch (RuntimeException e) {
System.out.println(label + " => " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
public static void main(String[] args) {
int[] scores = {70, 85, 90};
// 原因1: 条件が i <= length
show("1. i <= length", () -> {
int sum = 0;
for (int i = 0; i <= scores.length; i++) sum += scores[i];
return sum;
});
// 原因2: 空配列の先頭を読む
int[] empty = new int[0];
show("2. 空配列の [0]", () -> empty[0]);
show("2. \"\".split(\",\") の長さ", () -> "".split(",").length);
show("2. \",\".split(\",\") の長さ", () -> ",".split(",").length);
show("2. \",\".split(\",\")[0]", () -> ",".split(",")[0]);
// 原因3: コマンドライン引数を付けずに args[0] を読む
show("3. args.length", () -> args.length);
show("3. args[0]", () -> args[0]);
// 原因4: 多次元配列の行と列の取り違え(2行3列)
int[][] table = { {1, 2, 3}, {4, 5, 6} };
show("4. table[2][0](行は 0〜1)", () -> table[2][0]);
show("4. table[1][3](列は 0〜2)", () -> table[1][3]);
show("4. 行と列を逆にしたループ", () -> {
int sum = 0;
for (int col = 0; col < table[0].length; col++)
for (int row = 0; row < table.length; row++)
sum += table[col][row];
return sum;
});
int[][] jagged = { {1, 2, 3}, {4} };
show("4. 行ごとに長さが違う配列 jagged[1][1]", () -> jagged[1][1]);
// 原因5: 負のインデックス
show("5. scores[-1]", () -> scores[-1]);
show("5. indexOf の -1 をそのまま使う", () -> scores["abc".indexOf("x")]);
show("5. 負数の % は負になる: -7 % 3", () -> -7 % 3);
show("5. scores[-7 % 3]", () -> scores[-7 % 3]);
show("5. Math.floorMod(-7, 3)", () -> Math.floorMod(-7, 3));
// 原因6: length - 1 の計算ずれ
show("6. 末尾のつもりで scores[scores.length]", () -> scores[scores.length]);
show("6. 隣と比べる scores[i + 1]", () -> {
int ups = 0;
for (int i = 0; i < scores.length; i++) if (scores[i + 1] > scores[i]) ups++;
return ups;
});
show("6. 空配列の末尾 empty[empty.length - 1]", () -> empty[empty.length - 1]);
// Objects.checkIndex(Java 9 以降)
show("checkIndex(2, 3)", () -> Objects.checkIndex(2, 3));
show("checkIndex(3, 3)", () -> Objects.checkIndex(3, 3));
show("checkIndex(-1, 3)", () -> Objects.checkIndex(-1, 3));
show("checkFromToIndex(1, 5, 3)", () -> Objects.checkFromToIndex(1, 5, 3));
}
}
実行結果(Java 21.0.10。11.0.32.1・17.0.20.1・25.0.4.1 もすべて同じ出力):
1. i <= length => ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
2. 空配列の [0] => ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
2. "".split(",") の長さ => 1
2. ",".split(",") の長さ => 0
2. ",".split(",")[0] => ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
3. args.length => 0
3. args[0] => ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
4. table[2][0](行は 0〜1) => ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
4. table[1][3](列は 0〜2) => ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
4. 行と列を逆にしたループ => ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
4. 行ごとに長さが違う配列 jagged[1][1] => ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
5. scores[-1] => ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
5. indexOf の -1 をそのまま使う => ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
5. 負数の % は負になる: -7 % 3 => -1
5. scores[-7 % 3] => ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
5. Math.floorMod(-7, 3) => 2
6. 末尾のつもりで scores[scores.length] => ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
6. 隣と比べる scores[i + 1] => ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
6. 空配列の末尾 empty[empty.length - 1] => ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 0
checkIndex(2, 3) => 2
checkIndex(3, 3) => IndexOutOfBoundsException: Index 3 out of bounds for length 3
checkIndex(-1, 3) => IndexOutOfBoundsException: Index -1 out of bounds for length 3
checkFromToIndex(1, 5, 3) => IndexOutOfBoundsException: Range [1, 5) out of bounds for length 3
原因別の修正コード
上の6つを直したコードです。
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
public class Fixed {
/** 範囲内なら値を、範囲外なら空を返す */
static OptionalInt getOrEmpty(int[] array, int index) {
if (array == null || index < 0 || index >= array.length) return OptionalInt.empty();
return OptionalInt.of(array[index]);
}
/** 範囲外なら、何が範囲外だったかを添えて知らせる */
static int scoreAt(int[] scores, int index) {
Objects.checkIndex(index, scores.length);
return scores[index];
}
public static void main(String[] args) {
int[] scores = {70, 85, 90};
int[] empty = new int[0];
// 1. 条件は i < length
int sum = 0;
for (int i = 0; i < scores.length; i++) sum += scores[i];
System.out.println("1. 合計 = " + sum);
// 1'. 番号が要らないなら拡張 for 文
int sum2 = 0;
for (int score : scores) sum2 += score;
System.out.println("1'. 拡張forの合計 = " + sum2 + " / Arrays.stream = " + Arrays.stream(scores).sum());
// 2. 空かどうかを先に確かめる
System.out.println("2. 先頭 = " + (empty.length > 0 ? String.valueOf(empty[0]) : "(要素なし)"));
// 3. 引数の数を確かめて使い方を表示する
if (args.length < 1) {
System.out.println("3. 使い方: java Fixed.java <名前>");
} else {
System.out.println("3. こんにちは、" + args[0] + " さん");
}
// 4. 行は table.length、列は table[row].length
int[][] jagged = { {1, 2, 3}, {4} };
int total = 0;
for (int row = 0; row < jagged.length; row++)
for (int col = 0; col < jagged[row].length; col++)
total += jagged[row][col];
System.out.println("4. 合計 = " + total);
// 5. 見つからない(-1)を先に分ける / 循環は floorMod
int pos = "abc".indexOf("x");
System.out.println("5. " + (pos >= 0 ? "位置 " + pos : "見つかりません"));
System.out.println("5. 循環: scores[Math.floorMod(-7, 3)] = " + scores[Math.floorMod(-7, scores.length)]);
// 6. 末尾は length - 1、隣と比べるループは length - 1 まで
System.out.println("6. 末尾 = " + scores[scores.length - 1]);
int ups = 0;
for (int i = 0; i < scores.length - 1; i++) if (scores[i + 1] > scores[i]) ups++;
System.out.println("6. 上がった回数 = " + ups);
// 予防: 範囲を確かめてから読む
System.out.println("getOrEmpty(scores, 1) = " + getOrEmpty(scores, 1));
System.out.println("getOrEmpty(scores, 3) = " + getOrEmpty(scores, 3));
System.out.println("getOrEmpty(empty, 0) = " + getOrEmpty(empty, 0));
System.out.println("scoreAt(scores, 2) = " + scoreAt(scores, 2));
try {
scoreAt(scores, 3);
} catch (IndexOutOfBoundsException e) {
System.out.println("scoreAt(scores, 3) => " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
// List の末尾は size() - 1
List<String> names = List.of("sato", "suzuki");
System.out.println("末尾の要素 = " + names.get(names.size() - 1));
System.out.println("AIOOBE の親 = " + ArrayIndexOutOfBoundsException.class.getSuperclass().getSimpleName()
+ " / その親 = " + IndexOutOfBoundsException.class.getSuperclass().getSimpleName());
}
}
実行結果(Java 21.0.10、引数なし。11.0.32.1・17.0.20.1・25.0.4.1 も同じ出力):
1. 合計 = 245
1'. 拡張forの合計 = 245 / Arrays.stream = 245
2. 先頭 = (要素なし)
3. 使い方: java Fixed.java <名前>
4. 合計 = 10
5. 見つかりません
5. 循環: scores[Math.floorMod(-7, 3)] = 90
6. 末尾 = 90
6. 上がった回数 = 2
getOrEmpty(scores, 1) = OptionalInt[85]
getOrEmpty(scores, 3) = OptionalInt.empty
getOrEmpty(empty, 0) = OptionalInt.empty
scoreAt(scores, 2) = 90
scoreAt(scores, 3) => IndexOutOfBoundsException: Index 3 out of bounds for length 3
末尾の要素 = suzuki
AIOOBE の親 = IndexOutOfBoundsException / その親 = RuntimeException
java Fixed.java 田中 のように引数を付けると、3. の行は 3. こんにちは、田中 さん に変わりました。以下、原因ごとにポイントを説明します。
原因1:ループ条件がi <= lengthになっている
もっとも多い原因です。配列の番号は 0 から始まるので、最後の番号は length - 1です。<= で回すと、最後の1回で a[length] を読みに行きます。
for (int i = 0; i <= scores.length; i++) // 直す前:i が 3 まで進む
for (int i = 0; i < scores.length; i++) // 直した後:i は 0, 1, 2
番号そのものが要らない処理なら、拡張for文(for (int score : scores))にすると番号を書かずに済み、この原因は起きなくなります。合計だけなら Arrays.stream(scores).sum() でも同じ結果(245)でした。
原因2:空配列の先頭を読んでいる
new int[0] のように長さ 0 の配列は正しく作れますが、使える番号が1つもありません。検索結果が0件だった、ファイルが空だった、というときに起きます。
注目したいのは split の結果です。実行結果のとおり、"".split(",") の長さは 1、",".split(",") の長さは 0 でした。split は末尾の空文字を取り除くため、区切り文字だけの文字列は空配列になります。split の結果を [0] や [1] で読む前に、length を確かめておくと安心です。
String[] parts = line.split(",");
if (parts.length < 2) {
System.out.println("列が足りません: [" + line + "]");
} else {
String name = parts[1];
}
原因3:コマンドライン引数を付けずにargs[0]を読んでいる
java Main のように引数なしで起動すると、args は null ではなく長さ 0 の配列になります(実行結果の args.length => 0)。そのため args[0] は NullPointerException ではなく、ArrayIndexOutOfBoundsException になります。
args.length を確かめ、足りないときは使い方を表示して終えるのが定番の書き方です。IDE から実行している場合は、実行構成の「プログラムの引数」欄に値を入れます。
原因4:多次元配列の行と列を取り違えている
int[][] table = { {1, 2, 3}, {4, 5, 6} }; は2行3列です。Java の2次元配列は「配列の配列」なので、長さは2種類あります。
| 式 | 意味 | この例の値 |
|---|---|---|
table.length |
行の数 | 2 |
table[0].length |
0行目の列の数 | 3 |
table[row][col] |
先に行、次に列 | table[1][2] は 6 |
メッセージの length が行の数(2)なのか列の数(3)なのかを見ると、どちらの番号が範囲外かがわかります。table[2][0] は for length 2、table[1][3] は for length 3 でした。
行ごとに長さが違う配列({ {1, 2, 3}, {4} })もあります。内側のループ条件を table[0].length ではなく table[row].length にすると、どの形の配列でも範囲内に収まります。
原因5:負のインデックスを使っている
Java の配列では、-1 が「末尾」を表すことはありません。負数はすべて範囲外です。負数が入り込む経路は主に3つあります。
| 経路 | 例 | 直し方 |
|---|---|---|
| 見つからないときの -1 | "abc".indexOf("x") は -1 |
pos >= 0 を確かめてから使う |
| 負数の剰余 | -7 % 3 は -1 |
Math.floorMod(-7, 3)(結果は 2) |
| 引き算の結果 | 空配列で length - 1 |
空かどうかを先に確かめる |
循環するように番号を進めたいとき(曜日や順番の繰り返しなど)は、% の代わりに Math.floorMod を使うと、結果が必ず 0 以上になります。
原因6:length - 1の計算がずれている
末尾の要素は scores[scores.length - 1] です。scores[scores.length] と書くと1つ先を指します。
隣どうしを比べるループも、ずれやすい場所です。scores[i + 1] を読むなら、i は最後の1つ手前で止めます。
for (int i = 0; i < scores.length; i++) if (scores[i + 1] > scores[i]) ... // 直す前
for (int i = 0; i < scores.length - 1; i++) if (scores[i + 1] > scores[i]) ... // 直した後
ループの中で読む番号のうち、いちばん大きいものが length - 1 以下になるかを確かめると、条件を正しく決められます。i + 1 を読むなら i + 1 <= length - 1、つまり i < length - 1 です。
例外を防ぐ書き方
| 方法 | 書き方 | 向いている場面 |
|---|---|---|
| 拡張for文 | for (int v : a) |
全要素を順に処理する |
| Stream | Arrays.stream(a).sum() |
合計・最大・絞り込み |
| 範囲を確かめる if | if (0 <= i && i < a.length) |
番号を外から受け取る |
| Optional で返す | getOrEmpty(a, i)(Fixed.java) |
範囲外を「値なし」として扱いたい |
Objects.checkIndex |
Objects.checkIndex(i, a.length) |
範囲外をすぐ知らせたい |
Objects.checkIndexで範囲を確かめる
Objects.checkIndex(index, length) は Java 9 で追加されたメソッドで、番号が 0 以上 length 未満なら番号をそのまま返し、範囲外なら IndexOutOfBoundsException を投げます。実行結果のとおり、メッセージは配列と同じ Index 3 out of bounds for length 3 の形です。
- 範囲の開始と終了を確かめるなら
Objects.checkFromToIndex(from, to, length)(Range [1, 5) out of bounds for length 3) - 投げられる例外は ArrayIndexOutOfBoundsException ではなく、親の IndexOutOfBoundsException
- 自作のクラスで「番号を受け取るメソッド」の先頭に置くと、どの引数が範囲外だったかが呼び出し側に伝わります
try-catchで受ける場面
範囲外は、多くの場合コードを直して防げます。try-catch を使うのは、外から来た番号をまとめて受けたい場合などに絞り、catch の中では番号と長さをログに残すようにします。空の catch で握りつぶすと、原因をたどる手がかりがなくなります。
よくある質問
Q. ArrayIndexOutOfBoundsException と IndexOutOfBoundsException の違いは?
A. IndexOutOfBoundsException が親で、配列用の子クラスが ArrayIndexOutOfBoundsException、文字列用の子クラスが StringIndexOutOfBoundsException です。ArrayList の get は親の IndexOutOfBoundsException を投げます。
Q. メッセージに数字が1つしか出ません。
A. Java 8 では ArrayIndexOutOfBoundsException: 3 のように、使った番号だけが表示されます。Java 11 以降で動かすと Index 3 out of bounds for length 3 の形になり、配列の長さもわかります。
Q. コンパイルは通るのに、実行すると止まるのはなぜですか? A. 番号が範囲内かどうかは、実行して値が決まるまでわからないためです。RuntimeException の系統なので、try-catch を書かなくてもコンパイルは通ります。
Q. 配列の末尾を -1 で指定できますか?
A. できません。Java では a[a.length - 1] と書きます。空配列ではこの式が -1 になるので、先に a.length > 0 を確かめます。
まとめ
Index 3 out of bounds for length 3は「番号 3 を使ったが、長さは 3」。使える番号は 0 〜 length - 1 です。- スタックトレースは、at の行のうち最初に出てくる自分のクラスの行番号から開きます。
- Java 8 のメッセージは数字だけで、その数字は使おうとした番号です。
- Index と length が同じなら
<=やa[length]、length が 0 なら空配列や args、負数なら indexOf や剰余を疑います。 - 配列は
length、文字列はlength()、リストはsize()。例外名もそれぞれ違い、親は IndexOutOfBoundsException です。 - 多次元配列は、行が
table.length、列がtable[row].lengthです。 - 拡張for文・範囲チェック・
Objects.checkIndexで、範囲外を前もって防げます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java ArrayIndexOutOfBoundsException原因と対処」
株式会社RJCでは、Javaの配列と例外の読み方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。