HOWTO · Java
配列から要素を削除してから、Java で他の要素をシフトする
この記事では、配列から要素を削除して配列をシフトする方法を紹介します。
このページの内容
Java 配列の長さは固定です。値を左にずらして空きを埋めても、配列の長さは短くなりません。要素数が 1 つ少ない配列を返すには、より小さい配列を作るか、サイズ変更可能なコレクションを使います。以下の例では 0 から始まるインデックスで要素を削除し、最後の節では値を指定して削除する方法を説明します。
ループで要素を左にずらして配列を縮小する
選択したインデックスより後ろの各要素を 1 つ前の位置にコピーし、その後 Arrays.copyOf で短い配列を作ります。ずらす処理では元の配列が変更されるため、元の値を残す必要がある場合は、あらかじめ別のコピーを作ってください。
有効なインデックスの範囲は 0 から array.length - 1 までです。この例では、インデックスを読み取る前に範囲を確認します。範囲外の場合は配列を変更せず、メッセージを表示します。コードに含まれる範囲外メッセージ「Index out of bounds」は、例のコードをそのまま保つため英語のままです。
Java 配列の長さは固定です。後続の値を前の位置に代入すると残りの要素は左にずれますが、配列の長さは元のままです。要素数が 1 つ少ない配列を返すには、ずらした部分を新しい配列にコピーします。次の例を実行してください。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
int indexToRemove = 2;
if (indexToRemove >= 0 && indexToRemove < array.length) {
for (int i = indexToRemove; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array = Arrays.copyOf(array, array.length - 1);
System.out.println(Arrays.toString(array));
} else {
System.out.println("Index out of bounds");
}
}
}
出力:
[1, 2, 4, 5]
各反復では array[i + 1] を読み取るため、ループは元の配列の最後から 2 番目のインデックスまで進みます。要素をずらした後、元の最後の位置には値が重複して残ります。Arrays.copyOf は 1 つ短い配列を割り当て、その位置を切り捨てます。最後の要素を削除する場合も同じように処理できます。この場合、ループは一度も実行されず、コピーによって最後の位置が直接取り除かれます。
System.arraycopy() でインデックスの前後をコピーする
System.arraycopy() を使うと、削除する要素より前と後ろの部分を、新しい配列の別々の範囲にコピーできます。引数は、コピー元、コピー元の位置、コピー先、コピー先の位置、コピーする要素数の順です。最初のコピーの長さは削除するインデックスで、2 回目の長さは array.length - indexToRemove - 1 です。範囲外を示すコード内のメッセージ「Index out of bounds」は、コードをそのまま保つため英語のままです。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {2, 4, 6, 8, 10};
int indexToRemove = 2;
if (indexToRemove >= 0 && indexToRemove < array.length) {
int[] result = new int[array.length - 1];
System.arraycopy(array, 0, result, 0, indexToRemove);
int elementsAfter = array.length - indexToRemove - 1;
System.arraycopy(array, indexToRemove + 1, result, indexToRemove, elementsAfter);
System.out.println(Arrays.toString(result));
} else {
System.out.println("Index out of bounds");
}
}
}
出力:
[2, 4, 8, 10]
ここではコピー先の配列を必要な長さで作成しているため、末尾に余分な既定値は入りません。System.arraycopy() は、コピー元とコピー先が同じ配列でコピー範囲が重なる場合にも対応しており、Java が安全にコピーを処理します。上の例のように別の配列へコピーすると、残す先頭部分と末尾部分が明確になります。
ArrayList を使って要素を削除する
データがすでにコレクションに入っている場合や、追加と削除を複数回行う場合は ArrayList<Integer> を使います。int[] を整数のリストとしてそのまま Arrays.asList に渡すことはできません。プリミティブ配列は単一のオブジェクトとして扱われるためです。値を Integer[] にボックス化してリストを作成し、インデックスで要素を削除してから int[] に戻します。範囲外を示すコード内のメッセージ「Index out of bounds」は、コードをそのまま保つため英語のままです。
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
int indexToRemove = 2;
if (indexToRemove >= 0 && indexToRemove < array.length) {
Integer[] boxed = Arrays.stream(array).boxed().toArray(Integer[]::new);
ArrayList<Integer> values = new ArrayList<>(Arrays.asList(boxed));
values.remove(indexToRemove);
int[] result = values.stream().mapToInt(Integer::intValue).toArray();
System.out.println(Arrays.toString(result));
} else {
System.out.println("Index out of bounds");
}
}
}
出力:
[1, 2, 4, 5]
values.remove(indexToRemove) は、指定した位置の要素を削除するオーバーロードを選びます。リストにはボックス化された Integer オブジェクトが格納されている一方、元のデータはプリミティブ型の int なので、最後に int[] へ変換します。この方法は便利ですが、リストと新しい配列を作成します。既存の int[] から 1 つだけ削除するなら、どちらの配列コピー方法でもコレクションへの変換を避けられます。
インデックスまたは値で選び、端のケースに対処する
各例では、0 から始まるインデックスを使って 1 つの要素を削除します。有効なインデックスは index >= 0 && index < array.length を満たします。この条件を満たさない場合に呼び出し側でどう対応するかを決めてください。元の配列を返す、状態を返す、または問題を通知する方法があります。範囲の確認に成功するまでは array[index] にアクセスしないでください。要素のない配列には有効なインデックスがないため、同じ確認ですべてのインデックスを安全に拒否できます。
値を指定して削除するには、最初に一致するインデックスを探し、同じ要素の移動と配列縮小の処理を使います。この例で削除されるのは最初の一致だけです。一致する値がなければ元の配列は変更されず、最初の一致より後にある同じ値も結果に残ります。
このセクションのコードと表示されるラベルや出力は英語のままです。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {3, 7, 3, 9};
int target = 3;
int indexToRemove = -1;
for (int i = 0; i < array.length; i++) {
if (array[i] == target) {
indexToRemove = i;
break;
}
}
if (indexToRemove >= 0) {
int[] result = new int[array.length - 1];
for (int i = 0; i < indexToRemove; i++) {
result[i] = array[i];
}
for (int i = indexToRemove + 1; i < array.length; i++) {
result[i - 1] = array[i];
}
array = result;
}
System.out.println(Arrays.toString(array));
}
}
この入力では最初の 3 が削除され、結果は [7, 3, 9] になります。target が見つからない場合、indexToRemove は -1 のままなのでコピーは実行されず、元の配列が変更されずに表示されます。すべての一致を削除するには、まず一致しない値の数を数えて結果配列の長さを決め、その値だけをコピーします。
各要素の移動を確認して制御したい場合はループを選びます。2 つの範囲から適切な長さの配列を作る処理を明確にしたい場合は System.arraycopy() を選びます。周囲のコードですでにサイズ変更可能なコレクションが必要な場合や、複数回編集する場合は ArrayList を選びます。どの方法でも、削除した要素を含まない新しい論理的な並びを作れます。入力配列の内容も変更してから短いコピーを返すのは、最初のループ方式だけです。