はじめに (対象読者・この記事でわかること)

この記事は、Javaのプログラミング初心者から中級者を対象にしています。配列操作に不慣れな方や、要素の削除方法でエラーに悩んでいる方に向け、配列から特定の要素を安全に除くやり方をわかりやすく解説します。この記事を読むと、以下ができるようになります。

  • 配列のインデックスや要素の検索方法を理解できる
  • System.arraycopyArrayList への変換を用いた要素除去の実装ができる
  • 除去処理で起こりやすい ArrayIndexOutOfBoundsExceptionConcurrentModificationException の回避策が身につく

配列操作は基礎的ながらもバグが潜みがちです。実務でのコード品質向上の一助として、ぜひ活用してください。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • Java の基本的な文法(変数宣言、ループ、条件分岐)
  • 配列(int[], String[] など)の宣言と基本操作
  • List インターフェースと ArrayList の概要

配列要素を除くとは何か

配列は固定長のデータ構造であるため、要素を直接削除することはできません。削除したい要素が判明したら、次のいずれかの手法で新しい配列を作り直す必要があります。

  1. 手動でコピー
    除外したいインデックス以外の要素をすべて新しい配列へコピーする。System.arraycopy を使うと高速です。
  2. コレクションへ変換
    配列を ArrayList に変換し、remove(Object)remove(int) を利用して削除した後、toArray で再度配列に戻す。
  3. ストリーム API
    Java 8 以降は Arrays.streamfilter を組み合わせて、条件に合致しない要素だけを収集し、toArray で配列化できる。

それぞれの手法は「パフォーマンス」と「可読性」のトレードオフがあります。例えば、数千要素程度の小規模配列なら ArrayList への変換がコードをすっきりさせますが、リアルタイム処理が要求される大規模配列では System.arraycopy が有利です。

配列要素の除去実装方法

以下では、実務でよく使われる 3 つのパターンを順に解説します。サンプルコードはすべて Java 17 で動作確認済みです。

ステップ1:除去対象のインデックス・要素を特定する

まずは除去したい要素が何か、あるいは除去対象のインデックスが何かを決めます。代表的な取得方法は次の通りです。

Java
// 例:int 配列から値が 5 の要素を探す int[] data = {1, 3, 5, 7, 5, 9}; int target = 5; List<Integer> indices = new ArrayList<>(); for (int i = 0; i < data.length; i++) { if (data[i] == target) { indices.add(i); // 削除対象インデックスを記録 } } System.out.println(indices); // => [2, 4]

上記のようにインデックスをリスト化すれば、後続のコピー処理がシンプルになります。複数要素を同時に削除したい場合は必ずインデックスを昇順にソートしておきましょう。逆順に処理するとインデックスがずれ、想定外の要素が削除される危険があります。

ステップ2:実装パターン別に除去処理を行う

1. System.arraycopy を利用した手動コピー

Java
public static int[] removeByIndex(int[] src, int removeIdx) { if (removeIdx < 0 || removeIdx >= src.length) { throw new IllegalArgumentException("インデックスが範囲外です"); } int[] dest = new int[src.length - 1]; // 前半部分をコピー System.arraycopy(src, 0, dest, 0, removeIdx); // 後半部分をコピー System.arraycopy(src, removeIdx + 1, dest, removeIdx, src.length - removeIdx - 1); return dest; } // 複数インデックスを削除する場合 public static int[] removeMultiple(int[] src, List<Integer> removeIdxList) { Collections.sort(removeIdxList, Collections.reverseOrder()); // 逆順にソート int[] result = src.clone(); for (int idx : removeIdxList) { result = removeByIndex(result, idx); } return result; }

ポイントは 逆順に削除 することです。これにより、削除した直後に残りのインデックスがシフトする問題を防げます。System.arraycopy はネイティブ実装のため、特に大規模配列でのパフォーマンスが優れています。

2. ArrayList へ変換して削除

Java
public static int[] removeByValue(int[] src, int value) { List<Integer> list = new ArrayList<>(src.length); for (int v : src) { list.add(v); } // 値が一致する要素をすべて削除 list.removeIf(e -> e == value); // 配列へ戻す return list.stream().mapToInt(Integer::intValue).toArray(); }

この手法は 可読性 が高く、削除ロジックを一行で表現できます。removeIf は内部でイテレータを安全に走査するため、ConcurrentModificationException が発生しません。ただし、オブジェクトラッピングintInteger)が発生するため、メモリ使用量が増える点に注意が必要です。

3. ストリーム API を利用したフィルタリング

Java
public static String[] filterOut(String[] src, String exclude) { return Arrays.stream(src) .filter(s -> !s.equals(exclude)) .toArray(String[]::new); }

ストリームは 宣言的 なコードを書ける点が魅力です。特に文字列やオブジェクト配列で「条件に合致しない要素だけ残す」ケースに有効です。ただし、内部的に一時的な Object[] が生成されるため、配列のサイズが極端に大きい場合は GC の負荷が上がります。

ハマった点やエラー解決

状況 発生したエラー 原因 解決策
インデックスが負数 IllegalArgumentException 無効なインデックスを removeByIndex に渡した 事前に範囲チェック (if (idx < 0 || idx >= arr.length)) を入れる
複数削除でインデックスずれ 期待通りに要素が残らない インデックスを昇順のまま削除した インデックスリストを 逆順 にソートしてから削除
ArrayList 変換時の型不一致 ClassCastException int[]List<Integer> の変換で自動ボクシングを忘れた list.add(v) のように明示的に Integer に変換
ストリームで null が混入 NullPointerException filter 内で s.equals(...) を呼んだが要素が null Objects.equals(s, exclude)s != null && ... でガード

解決策

上記の問題は、入力の検証と処理順序の管理 が鍵です。特に配列はサイズが固定でインデックスがずれやすいので、次のベストプラクティスを守りましょう。

  1. インデックス取得は必ずリスト化し、削除は逆順に行う
  2. 配列 → リスト変換時は必ずボクシングが発生することを意識し、メモリ使用量を測定する
  3. ストリーム使用時は null 安全処理を忘れない
  4. 例外は具体的に捕捉し、原因をログに残す(例:IllegalArgumentException にメッセージを付与)

これらを実践するだけで、配列要素除去に関するバグは格段に減ります。

まとめ

本記事では、Java 配列から要素を除く 3 つの代表的手法System.arraycopyArrayList 変換、ストリーム)と、それぞれの実装ポイント・注意点を詳しく解説しました。

  • 手動コピーは高速だがインデックス管理が必須。
  • ArrayList 変換は可読性が高く、removeIf が便利。
  • ストリームは宣言的でシンプルだが、メモリコストに留意。

これらを適材適所で使い分けることで、配列操作の信頼性とパフォーマンスを両立できます。次回は「可変長配列を自前で実装する」や「**マルチスレッド環境での配列操作」」といった、さらに高度なテクニックを紹介する予定です。

参考資料