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(挿入位置も範囲チェックあり)
JavaList#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 が有効)
Javasubstring の第二引数は「終端の直後(排他)」です。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(); // 安全に削除
}
JavasubList は「ビュー」であること
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 系に任せてもよい——この型が身につけば、範囲外アクセスは設計段階で消せます。
