HOWTO · Java
Java で動的配列を作成する
このガイドでは、Java で動的配列を作成する方法について説明します。
このページの内容
Javaの配列は、作成後に長さを変更できません。その長さを超えて要素を追加するには、通常の配列をdynamic arrayクラスで包み、格納中の値の数を管理し、空きがなくなったときに内部配列を置き換えます。以下の例では、プリミティブ型のint値を末尾に追加し、最後の値を削除できます。また、現在のsizeに合わせて内部配列を縮小することもできます。
Javaで独自のロジックを使って動的配列を作成する
このクラスは、内部のint[]、size、capacityという3つの状態を管理します。sizeは現在格納されている値の数、capacityは内部配列の長さです。これらを別々に管理することで、配列に空き領域を確保しつつ、その領域を格納済みの値として扱わずに済みます。
次の例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
import java.util.Arrays;
public class DynamicArray {
private int[] values;
private int size;
public DynamicArray() {
values = new int[2];
}
public void add(int value) {
if (size == values.length) {
int newCapacity = values.length == 0 ? 1 : values.length * 2;
values = Arrays.copyOf(values, newCapacity);
}
values[size] = value;
size++;
}
public void removeLast() {
if (size == 0) {
return;
}
size--;
values[size] = 0;
}
public void shrinkToSize() {
values = Arrays.copyOf(values, size);
}
public int size() {
return size;
}
public int capacity() {
return values.length;
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(values, size));
}
public static void main(String[] args) {
DynamicArray numbers = new DynamicArray();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);
numbers.add(50);
System.out.println(numbers + " | size=" + numbers.size()
+ " | capacity=" + numbers.capacity());
numbers.removeLast();
System.out.println(numbers + " | size=" + numbers.size()
+ " | capacity=" + numbers.capacity());
numbers.shrinkToSize();
System.out.println(numbers + " | size=" + numbers.size()
+ " | capacity=" + numbers.capacity());
}
}
出力例:
[10, 20, 30, 40, 50] | size=5 | capacity=8
[10, 20, 30, 40] | size=4 | capacity=8
[10, 20, 30, 40] | size=4 | capacity=4
5回追加すると、値は[10, 20, 30, 40, 50]となり、サイズは5、容量は8です。removeLast()を呼び出すと、値は[10, 20, 30, 40]となり、サイズは4、容量は8です。その後shrinkToSize()を呼び出すと、サイズと容量はいずれも4になります。コード例と出力例の表示ラベルは、変更されず英語のままです。
サイズ、容量、配列の拡張を理解する
コンストラクターは容量を2、サイズを0に設定します。addを呼び出すたびに、サイズが容量に達しているか確認します。内部配列が満杯の場合、Arrays.copyOfが元の2倍の長さの配列を作成し、既存の値をコピーします。続いて次の値をsizeの位置に書き込み、サイズを1増やします。
例のように5回追加すると、容量は2から4、さらに8へと変わります。拡張時には格納済みの値がコピーされ、新しい配列に残った空き領域は後から追加する値に使えます。容量が示すのは確保された領域であり、値の数ではありません。toString()メソッドはサイズより前の部分だけをコピーするため、空き領域は要素として表示されません。
コピーにかかる時間は、コピーする値の数に比例します。また、コピー中は元の配列と新しい配列の両方が一時的に必要です。容量を2倍にすれば、追加のたびにコピーする必要はありません。ただし、拡張が発生する個々の追加ではコピーが行われます。この小さなクラスは仕組みを示すための例です。一般的なコレクションには、通常、JavaのArrayList<Integer>のほうが適しています。
要素を追加・削除する
addが対応するのは末尾への追加だけで、任意のインデックスへの挿入はできません。removeLastはサイズを1減らし、空いた位置をJavaのintの既定値である0にします。intは参照ではなくプリミティブ値なので、ガベージコレクションのためにその位置をクリアする必要はありません。未使用領域の値を予測しやすくするためにクリアしています。サイズが0のときにremoveLastを呼び出しても、オブジェクトの状態は変わりません。
削除しても容量は自動的には縮小されません。今後の追加に備えて空き領域を保つことより、未使用の配列領域を解放することを優先したい場合は、shrinkToSize()を呼び出します。縮小時にも、新しい配列へ値をコピーします。サイズが0なら容量も0になり、次にaddを呼び出すと、新しい値を格納する前に容量が1へ拡張されます。この実装が格納するのはプリミティブ型のint値だけで、インデックスによるアクセス、挿入、中間要素の削除には対応していません。