配列コピーの全体像
「配列のコピー」は、既存の配列から新しい配列を作って中身を複製することです。Java では System.arraycopy、Arrays.copyOf、Arrays.copyOfRange、clone() が代表的な方法です。重要なのは「浅いコピー(要素参照をそのまま複製)」と「深いコピー(要素内容も再構築)」の違いを理解し、用途に合わせて使い分けることです。プリミティブ配列は値が複製され、オブジェクト配列は参照が複製されるため、変更が伝播するかどうかが根本的に違います。
標準的なコピー手段
Arrays.copyOf(サイズ変更も可能)
Arrays.copyOf(src, newLength) は最も簡単で安全な「丸ごとコピー」。サイズを変えられるため、拡張や縮小にも使えます。
int[] src = {1, 2, 3};
int[] dst = java.util.Arrays.copyOf(src, 5); // [1,2,3,0,0](拡張、末尾は0で埋まる)
JavaArrays.copyOfRange(部分コピー)
区間 [from, to)(from 包括・to 排他)でコピーします。範囲外なら例外、足りない分は型のデフォルト値で埋まります。
int[] src = {10, 20, 30, 40, 50};
int[] sub = java.util.Arrays.copyOfRange(src, 1, 4); // [20,30,40]
JavaSystem.arraycopy(最速の低レベル)
コピー先を自前で用意して、区間を指定して転送します。大量コピーや既存配列の一部上書きに向いています。
int[] src = {10, 20, 30, 40, 50};
int[] dst = new int[3];
System.arraycopy(src, 1, dst, 0, 3); // src[1..3] → dst[0..2] → [20,30,40]
Javaclone(丸ごとの浅いコピー)
array.clone() は同型・同サイズで丸ごとコピーします。中身がプリミティブなら値の複製、オブジェクト配列なら参照の複製(浅いコピー)です。
String[] a = {"A", "B"};
String[] b = a.clone(); // 参照は同じ要素を指す(浅い)
Java浅いコピーと深いコピー(ここが最重要)
プリミティブ配列は「値コピー」
- 結果: 片方を変更しても、もう片方には影響しません。
int[] a = {1, 2};
int[] b = a.clone();
b[0] = 99;
System.out.println(java.util.Arrays.toString(a)); // [1,2](影響なし)
Javaオブジェクト配列は「参照コピー」(浅い)
- 結果: 要素オブジェクトを更新すると、両方から同じオブジェクトが見えるため変更が伝播します。
class Box { int v; Box(int v){this.v=v;} }
Box[] a = { new Box(1), new Box(2) };
Box[] b = a.clone(); // 参照を複製
b[0].v = 99; // 同じ Box を指している
System.out.println(a[0].v); // 99(伝播)
Java深いコピーが必要なとき
- 方針: 各要素を新規インスタンスに置き換える(コピーコンストラクタ、
copy()メソッド、レコードなら成分で再構築)。
Box[] deep = java.util.Arrays.stream(a)
.map(x -> new Box(x.v)) // 新しい Box を作る
.toArray(Box[]::new);
deep[0].v = 7;
System.out.println(a[0].v); // 99(元は不変)
Java2次元配列(配列の配列)は「外側だけ clone しても内側は同じ参照」のままなので、内側も個別にコピーしないと深いコピーにはなりません。
性能とメモリの観点
System.arraycopy は高速
- 用途: 大量の連続領域コピーに最適。
Arrays.copyOf/copyOfRangeも内部で使うため十分速いですが、既存配列へ部分上書きする場面ではarraycopyが直接的です。
小さなコピーは可読性優先
- 指針: 使い分けで迷ったら
Arrays.copyOfを基本に。コードが短く、境界の取り違えが減ります。
不要なコピーは避ける
- 例: イミュータブルな配列を使う場面で毎回コピーするとメモリ・CPUを消耗します。変更しない契約なら共有、変更する可能性があるなら防御的コピー(受け取り時に一度だけコピー)を選びます。
よくある落とし穴と対策
浅いコピーで意図せず共有
- 対策: 要素が可変なら、必要に応じて深いコピーに切り替える。コピー後に変更する側が「独立性」を必要とする場合は要素再構築が必須。
範囲指定の勘違い(排他・包括)
- 対策:
copyOfRange(src, from, to)のtoは「排他」。インデックスは常に「0 ≤ index < length」。System.arraycopyのlengthは「要素数」であり、終端インデックスではありません。
2次元配列を clone して安心しない
- 対策: 外側 clone の後、各行(内側配列)を個別に
clone/copyOfする。
int[][] g = {{1,2},{3,4}};
int[][] shallow = g.clone(); // 行参照は共有
int[][] deep = new int[g.length][];
for (int i = 0; i < g.length; i++) deep[i] = g[i].clone(); // 行もコピー
JavaList に変換したときの「ビュー」扱いの違い
- 対策:
Arrays.asList(array)は固定サイズのリストビュー。要素変更は反映されるが、追加・削除は不可。独立した可変リストが欲しいならnew ArrayList<>(Arrays.asList(array))。
例題で身につける
例 1: 配列の一部を別配列へコピー(最小コスト)
int[] src = {10, 20, 30, 40, 50};
int[] dst = new int[3];
System.arraycopy(src, 1, dst, 0, 3); // [20,30,40]
System.out.println(java.util.Arrays.toString(dst));
Java例 2: サイズ拡張しながらコピー(余白を確保)
int[] src = {1, 2, 3};
int[] bigger = java.util.Arrays.copyOf(src, src.length + 2); // [1,2,3,0,0]
Java例 3: オブジェクト配列の深いコピー(要素再構築)
class Box { int v; Box(int v){this.v=v;} }
Box[] a = { new Box(1), new Box(2) };
Box[] deep = java.util.Arrays.stream(a)
.map(b -> new Box(b.v))
.toArray(Box[]::new);
deep[0].v = 7;
System.out.println(a[0].v); // 1(独立)
Java例 4: 2次元配列の安全な深いコピー
int[][] g = {{1,2,3},{4,5,6}};
int[][] copy = new int[g.length][];
for (int r = 0; r < g.length; r++) {
copy[r] = java.util.Arrays.copyOf(g[r], g[r].length);
}
copy[0][0] = 99;
System.out.println(g[0][0]); // 1(元は不変)
Java例 5: 部分コピーでサブ配列を取り出す
int[] src = {0,1,2,3,4,5,6};
int[] evens = java.util.Arrays.copyOfRange(src, 0, src.length); // 全体
int[] middle = java.util.Arrays.copyOfRange(src, 2, 5); // [2,3,4]
Java仕上げのアドバイス(重要部分のまとめ)
配列コピーは「何を独立させたいか」を決めるのが最優先です。プリミティブは浅いコピーで十分、オブジェクトは浅いコピーだと参照共有になるため、変更が伝播します。独立性が必要なら要素の再構築で深いコピーにする。全体・部分・サイズ変更は Arrays.copyOf/Range、既存配列への転送は System.arraycopy、丸ごとは clone。範囲の排他・包括を誤らず、2次元は内側までコピー——この型が身につけば、安全かつ効率よく配列を扱えます。
