Java | 基礎文法:IndexOutOfBoundsException

Java Java
スポンサーリンク

IndexOutOfBoundsException の全体像

IndexOutOfBoundsException(IOBE)は「インデックスが有効範囲の外」を指したときに投げられる実行時例外です。配列、リスト、文字列のような“並び”を扱う場面で発生し、典型的には「負のインデックス」「上限を超えたインデックス」「境界の勘違い(排他・包括)」が原因です。配列は ArrayIndexOutOfBoundsException、文字列は StringIndexOutOfBoundsException という派生型が投げられますが、根本は同じ「範囲外アクセス」です。


どんなときに発生するか

配列の範囲外アクセス

int[] a = {10, 20, 30};
int x = a[3];   // ArrayIndexOutOfBoundsException(有効なのは 0,1,2)
int y = a[-1];  // ArrayIndexOutOfBoundsException(負のインデックスも不可)
Java

有効範囲は常に 0 から length-1 までです。length そのものは「要素数」であり、最後の要素のインデックスではありません。

リストの範囲外アクセス

java.util.List<String> list = java.util.List.of("A", "B", "C");
String s = list.get(3);   // IndexOutOfBoundsException(有効なのは 0..2)
list.add(5, "X");         // IndexOutOfBoundsException(挿入位置も範囲チェックあり)
Java

List#get/set/add(index)/remove(index) は必ず範囲チェックを行い、外れると IOBE を投げます。

文字列の範囲外アクセス

String s = "abc";
char c = s.charAt(3);        // StringIndexOutOfBoundsException(有効なのは 0..2)
String t = s.substring(1, 4); // StringIndexOutOfBoundsException(end は排他で 0..3 が有効)
Java

substring の第二引数は「終端の直後(排他)」です。begin は包括、end は排他——ここを誤ると例外になります。


重要ポイントの深掘り:境界の考え方

インデックスの一般原則

インデックスの有効範囲は常に「0 以上、サイズ未満」。つまり「0 ≤ index < size」。上限は “未満” であり “以下” ではありません。最後の要素のインデックスは size-1 です。

包括・排他(inclusive/exclusive)の統一

  • 配列・リストの単要素アクセスは「index が包括」。
  • 文字列やサブリストの区間指定は「begin が包括」「end が排他」。

この規則を覚えておくと、境界の勘違いが激減します。

off-by-one(±1 のズレ)を潰す

for ループは「未満」を使うのが基本です。

int[] a = {10, 20, 30};
for (int i = 0; i < a.length; i++) {  // i <= a.length はダメ
    System.out.println(a[i]);
}
Java

「≤」と「<」の取り違えが IOBE の最頻出原因です。


予防の作法と安全な書き方

for-each を使ってインデックスを手放す

java.util.List<String> list = java.util.List.of("A", "B", "C");
for (String s : list) {            // インデックス不要で安全
    System.out.println(s);
}
Java

インデックス管理をやめると、off-by-one と負インデックスを根本から避けられます。

事前に境界を検証する

static <T> T safeGet(java.util.List<T> list, int index) {
    if (index < 0 || index >= list.size())
        throw new IllegalArgumentException("index out of range: " + index);
    return list.get(index);
}
Java

「契約として範囲外は渡さない」をメソッド入口で保証すると、後続のコードが簡潔になります。

サブ区間(substring/subList)は一度計算してから使う

String s = "abcdef";
int begin = 2, end = 5; // 包括2、排他5 → "cde"
if (begin < 0 || end > s.length() || begin > end) throw new IllegalArgumentException();
String part = s.substring(begin, end);
Java

条件をまとめてチェックすることで、境界のバラバラな判定漏れを防げます。

Java 9+ の組み込みチェックを活用

int idx = java.util.Objects.checkIndex(3, list.size());                   // 0..size-1 の検証
java.util.Objects.checkFromToIndex(2, 5, s.length());                     // [from, to) の検証
java.util.Objects.checkFromIndexSize(2, 3, s.length());                   // from..from+size の検証
Java

「正しい境界か」を標準 API に任せると、例外メッセージも分かりやすくなります。


よくある落とし穴と避け方

長さゼロの扱い

サイズが 0 の配列・リストは「どのインデックスも無効」です。0 でアクセスできると誤解しないこと。

java.util.List<String> empty = java.util.List.of();
// empty.get(0); // IOBE
Java

サイズ変化と古いインデックス

可変のリストに要素を追加・削除した後、以前に計算したインデックスは無効になり得ます。ループ中の remove は特に危険なので、Iterator を使うか、末尾から削除します。

var list = new java.util.ArrayList<>(java.util.List.of("A", "B", "C"));
var it = list.iterator();
while (it.hasNext()) {
    if ("B".equals(it.next())) it.remove(); // 安全に削除
}
Java

subList は「ビュー」であること

subList は元リストの一部へのビューです。元のリストのサイズが変わると、subList への操作で IOBE や ConcurrentModificationException が発生し得ます。必要なら新しいリストにコピーして独立させます。

var base = new java.util.ArrayList<>(java.util.List.of("A", "B", "C", "D"));
var view = base.subList(1, 3); // ["B","C"]
var copy = new java.util.ArrayList<>(view); // 独立コピー
Java

例題で身につける

例 1: 配列・リストの安全な走査

public class Traverse {
    public static void main(String[] args) {
        int[] a = {10, 20, 30};
        for (int i = 0; i < a.length; i++) System.out.println(a[i]); // 未満で回す

        var list = java.util.List.of("A", "B", "C");
        for (String s : list) System.out.println(s); // for-each で安全
    }
}
Java

例 2: substring の境界を一括チェック

public class Slice {
    static String slice(String s, int begin, int end) {
        if (begin < 0 || end > s.length() || begin > end)
            throw new IllegalArgumentException("range must satisfy 0<=begin<=end<=length");
        return s.substring(begin, end);
    }
    public static void main(String[] args) {
        System.out.println(slice("abcdef", 2, 5)); // "cde"
    }
}
Java

例 3: オブジェクトで範囲を表現して混乱を防ぐ

record Range(int begin, int end) {
    Range { if (begin < 0 || begin > end) throw new IllegalArgumentException(); }
    void validateLength(int length) {
        if (end > length) throw new IllegalArgumentException("end must be <= length");
    }
}
static String slice(String s, Range r) {
    r.validateLength(s.length());
    return s.substring(r.begin(), r.end());
}
Java

仕上げのアドバイス(重要部分のまとめ)

IndexOutOfBoundsException は「0 ≤ index < size」を破った瞬間に起きます。最後は size-1、区間は [begin, end)(begin 包括・end 排他)という原則を体に刻む。ループは “未満” で回し、for-each でインデックス管理を減らす。サイズ変化後の古いインデックスは再検証し、subList はビューだと理解して必要ならコピーする。検証は Objects.checkIndex 系に任せてもよい——この型が身につけば、範囲外アクセスは設計段階で消せます。

タイトルとURLをコピーしました