はじめに
配列の「コピー」は初心者がつまずきやすいポイントです。見た目は同じでも、実は「同じものを指しているだけ」になっていた…という混乱が起こりがち。ここでは、動く例で「安全にコピーする方法」と「浅いコピー/深いコピーの違い」を体感で覚えていきます。
代入はコピーではない
- ポイント:
=で配列を代入すると、同じ配列を共有します(別物は作られない)。 - 結果: 片方を変更すると、もう片方も変わります。
int[] src = {10, 20, 30};
int[] dst = src; // コピーではなく、同じ配列を指す
dst[0] = 999;
System.out.println(src[0]); // 999(srcも変わってしまう)
System.out.println(dst[0]); // 999
Java- 覚え方: 「
=は住所のメモ渡し」。家(配列そのもの)は1つ、住所を二人で持っているイメージ。
安全にコピーする3つの方法
1) for文で1つずつ手動コピー
- 使いどころ: 仕組みを理解したい、細かい制御がしたい。
- 長所: 直感的で万能。部分的にコピーするなど柔軟。
- 短所: コードが長くなる。
int[] src = {10, 20, 30};
int[] dst = new int[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
dst[0] = 999;
System.out.println(src[0]); // 10(独立しているので変わらない)
System.out.println(dst[0]); // 999
Java2) Arrays.copyOf を使う(いちばん簡単)
- 使いどころ: まるごとコピーしたいときの定番。
- 長所: 短くて読みやすい。長さを変えて拡張もできる。
- 短所: 部分コピーは不得意。
import java.util.Arrays;
int[] src = {10, 20, 30};
int[] dst = Arrays.copyOf(src, src.length); // 完全コピー
System.out.println(Arrays.toString(dst)); // [10, 20, 30]
// 長さを増やすことも可能(足りない分は0で埋まる)
int[] grown = Arrays.copyOf(src, 5);
System.out.println(Arrays.toString(grown)); // [10, 20, 30, 0, 0]
Java3) System.arraycopy を使う(高速で部分コピー向き)
- 使いどころ: 一部だけコピーしたい、大量データで速度を重視したい。
- 長所: 部分コピー・高速。
- 短所: 引数が多くてやや分かりにくい。
int[] src = {10, 20, 30, 40, 50};
int[] dst = new int[3];
// srcのインデックス1(20)から3要素ぶんをdstにコピー
System.arraycopy(src, 1, dst, 0, 3);
System.out.println(java.util.Arrays.toString(dst)); // [20, 30, 40]
Java参照型の落とし穴:浅いコピーと深いコピー
配列の中身が「数値や文字」などの値型なら問題は少ないですが、「オブジェクト」を入れる場合は注意が必要です。
浅いコピー(シャローコピー)
- 意味: 配列自体は新しくなるが、中のオブジェクトの参照は同じものを指す。
- 結果: 片方でオブジェクトの中身を変えると、もう片方も変わる。
class Person {
String name;
Person(String name) { this.name = name; }
}
Person[] src = { new Person("Alice"), new Person("Bob") };
// 配列は新しくなるが、中身(Person参照)は共有される
Person[] shallow = java.util.Arrays.copyOf(src, src.length);
shallow[0].name = "Changed";
System.out.println(src[0].name); // Changed(影響が及ぶ)
System.out.println(shallow[0].name); // Changed
Java深いコピー(ディープコピー)
- 意味: 中のオブジェクトも新しく作り直してコピーする。
- 結果: 片方を変えても、もう片方には影響しない。
class Person {
String name;
Person(String name) { this.name = name; }
}
Person[] src = { new Person("Alice"), new Person("Bob") };
// 1件ずつ新しいPersonを作ってコピーする(ディープコピー)
Person[] deep = new Person[src.length];
for (int i = 0; i < src.length; i++) {
deep[i] = new Person(src[i].name); // 中身を複製
}
deep[0].name = "Changed";
System.out.println(src[0].name); // Alice(影響なし)
System.out.println(deep[0].name); // Changed
Java- 覚え方: 「浅いコピー=箱だけ別、新しい中身は作らない」「深いコピー=箱も中身も別物」。
どれを使えばいい?簡単な目安
- とりあえず完全コピーしたい: Arrays.copyOf
- 一部だけ、または速度重視: System.arraycopy
- 学習や細かい制御: for文で手動コピー
- オブジェクト配列で安全に独立させたい: 深いコピー(中身を新しく作る)
練習課題
- 完全コピーの練習:
- タスク:
int[] a = {1,2,3,4}をArrays.copyOfでコピーし、片方だけ書き換えて違いを出力。 - ゴール: 「独立している」ことを確認する。
- タスク:
- 部分コピーの練習:
- タスク:
int[] b = {5,6,7,8,9}からSystem.arraycopyで中央の3つだけ取り出す。 - ゴール: 引数の意味(元の開始位置、コピー数、先の開始位置)を説明できる。
- タスク:
- 浅い/深いコピーの練習:
- タスク:
Bookクラス(titleだけ持つ)を使った配列で、浅いコピーと深いコピーをそれぞれ試す。 - ゴール: 「浅いコピーでは中身が共有される」感覚をつかむ。
- タスク:
解答例と解説
課題1:完全コピーの練習
問題:int[] a = {1,2,3,4} を Arrays.copyOf でコピーし、片方だけ書き換えて違いを出力。
解答例:
import java.util.Arrays;
public class CopyTest1 {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4};
int[] b = Arrays.copyOf(a, a.length); // 完全コピー
b[0] = 99; // コピー先だけ変更
System.out.println(Arrays.toString(a)); // [1, 2, 3, 4]
System.out.println(Arrays.toString(b)); // [99, 2, 3, 4]
}
}
Java解説:
Arrays.copyOfは新しい配列を作って中身をコピーする。bを変更してもaには影響しない。- 「独立した配列」ができていることを確認できる。
課題2:部分コピーの練習
問題:int[] b = {5,6,7,8,9} から System.arraycopy で中央の3つだけ取り出す。
解答例:
import java.util.Arrays;
public class CopyTest2 {
public static void main(String[] args) {
int[] b = {5, 6, 7, 8, 9};
int[] c = new int[3]; // コピー先の配列を用意
// bのインデックス1から3要素をcにコピー
System.arraycopy(b, 1, c, 0, 3);
System.out.println(Arrays.toString(c)); // [6, 7, 8]
}
}
Java解説:
System.arraycopy(元配列, 元の開始位置, コピー先配列, コピー先の開始位置, 要素数)- 今回は
b[1]から3つ →[6,7,8]がコピーされる。 - 部分コピーや高速コピーに便利。
課題3:浅いコピーと深いコピーの練習
問題:Book クラス(titleだけ持つ)を使った配列で、浅いコピーと深いコピーをそれぞれ試す。
浅いコピー(シャローコピー)
class Book {
String title;
Book(String title) { this.title = title; }
}
public class ShallowCopy {
public static void main(String[] args) {
Book[] src = { new Book("Java入門"), new Book("C入門") };
// 浅いコピー(参照だけコピー)
Book[] shallow = java.util.Arrays.copyOf(src, src.length);
shallow[0].title = "Python入門"; // コピー先の中身を変更
System.out.println(src[0].title); // Python入門(元も変わる)
System.out.println(shallow[0].title); // Python入門
}
}
Java解説:
- 配列自体は新しくなるが、中の
Bookオブジェクトは同じものを指す。 - 片方を変更するともう片方も変わる。
深いコピー(ディープコピー)
class Book {
String title;
Book(String title) { this.title = title; }
}
public class DeepCopy {
public static void main(String[] args) {
Book[] src = { new Book("Java入門"), new Book("C入門") };
// 深いコピー(中身も新しく作る)
Book[] deep = new Book[src.length];
for (int i = 0; i < src.length; i++) {
deep[i] = new Book(src[i].title);
}
deep[0].title = "Python入門"; // コピー先だけ変更
System.out.println(src[0].title); // Java入門(元は変わらない)
System.out.println(deep[0].title); // Python入門
}
}
Java解説:
- 新しい
Bookオブジェクトを作ってコピーするので、完全に独立。 - 片方を変更してももう片方には影響しない。
まとめ
- 課題1:
Arrays.copyOf→ 独立した配列が作れる。 - 課題2:
System.arraycopy→ 部分コピーや高速コピーに便利。 - 課題3: 浅いコピーは参照を共有、深いコピーは中身も新しく作る。
👉 ここまでで「配列コピーの基本」と「浅い/深いコピーの違い」が理解できるはずです。
次のステップとしては、実際にコードを動かして結果を確認することが大事です。
