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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?