1. はじめに
Java を勉強していると必ず出てくるキーワードの一つが 「コピー」 です。
コピーといっても、単に代入することと、新しいオブジェクトを生成して同じ内容を持たせることはまったく意味が違います。
さらにコピーには シャローコピー(浅いコピー) と ディープコピー(深いコピー) があり、この違いを正しく理解していないと、バグや予期しない挙動の原因になります。
本記事では以下を徹底的に解説します。
- Java におけるコピーの基本
- シャローコピーとディープコピーの違い
- 実際のコード例で挙動を確認
- よくある罠と注意点
- 実務に役立つ設計パターンとベストプラクティス
「コピー」という一見シンプルなテーマですが、ここを理解しているかどうかで、コードの安全性と拡張性に大きな差が出ます。
Java を学んでいる方はまず 絶対にJavaプログラマーになりたい人へ。 を読み、さらに転職やソースレビューを受けたい方は サイゼントアカデミー を利用すると良いでしょう。
2. コピーの基本
Javaの変数は参照である
Java のオブジェクト型変数は 参照(reference) を保持しています。
たとえば以下のコードを見てみましょう。
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
public class Main {
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = p1; // コピーではなく、同じ参照を渡しているだけ
p2.x = 10;
System.out.println(p1.x); // 10 と表示される
}
}
ここで p1 と p2 は 同じオブジェクト を指しているため、p2 を変更すると p1 にも影響します。
コピーとは、このような「参照共有」ではなく、別のオブジェクトを新しく作ること を指します。
3. シャローコピー(浅いコピー)とは
定義
シャローコピーは、オブジェクトを複製するが、その中の参照フィールドは「参照をコピーするだけ」で、中身のオブジェクト自体は複製しない方法です。
特徴
- プリミティブ型フィールドは値をコピーする
- 参照型フィールドは同じオブジェクトを共有する
- コピー元とコピー先が一部のデータを共有してしまう
コード例
class Address {
String city;
Address(String city) { this.city = city; }
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // シャローコピー
}
}
public class Main {
public static void main(String[] args) throws Exception {
Address addr = new Address("Tokyo");
Person p1 = new Person("Alice", addr);
Person p2 = p1.clone();
p2.address.city = "Osaka";
System.out.println(p1.address.city); // Osaka
}
}
この例では、p1 と p2 は別インスタンスですが、内部の Address は同じ参照を共有しているため、片方を変更するともう片方にも影響します。
4. ディープコピー(深いコピー)とは
定義
ディープコピーは、オブジェクトをコピーするだけでなく、内部にある参照型フィールドも再帰的にコピーして新しいオブジェクトを生成する方法です。
特徴
- コピー元とコピー先は完全に独立する
- 片方を変更してももう片方に影響しない
- 実装の手間は増える
コード例
class Address implements Cloneable {
String city;
Address(String city) { this.city = city; }
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = address.clone(); // ディープコピー
return cloned;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Address addr = new Address("Tokyo");
Person p1 = new Person("Bob", addr);
Person p2 = p1.clone();
p2.address.city = "Nagoya";
System.out.println(p1.address.city); // Tokyo
}
}
この場合、p1 と p2 は完全に独立しています。
5. シャローコピー vs ディープコピー比較表
| 項目 | シャローコピー | ディープコピー |
|---|---|---|
| プリミティブ型 | 値をコピー | 値をコピー |
| 参照型 | 参照を共有 | 新しいオブジェクトを生成 |
| 独立性 | 部分的に共有 | 完全に独立 |
| 実装コスト | 低い | 高い |
| パフォーマンス | 高速 | 遅い |
| 利用シーン | 不変オブジェクトを含む場合 | 変更が独立であるべき場合 |
6. よくある罠と注意点
- Cloneable と clone() の制約
- immutable オブジェクトは安全
- 循環参照の問題
- シリアライズを使ったコピー
7. 実践例:リストを持つクラスのコピー
import java.util.*;
class Book implements Cloneable {
String title;
List<String> pages;
Book(String title, List<String> pages) {
this.title = title;
this.pages = pages;
}
@Override
protected Book clone() throws CloneNotSupportedException {
return (Book) super.clone(); // シャローコピー
}
protected Book deepClone() throws CloneNotSupportedException {
Book copy = (Book) super.clone();
copy.pages = new ArrayList<>(pages); // 新しいリストを生成
return copy;
}
}
実行して挙動を比較すると、シャローコピーではページリストが共有され、ディープコピーでは独立します。
8. ベストプラクティスと設計パターン
- コピーコンストラクタを用意
deepClone()メソッドを別に作る- 不変クラス設計でコピー自体を不要にする
- Apache Commons Lang などのライブラリを活用
- コピー仕様をコメントや設計書に明記する
9. まとめ
- シャローコピー:参照を共有するコピー(速いが危険)
- ディープコピー:完全に独立するコピー(安全だが重い)
- 使い分けは状況次第。immutable が多い場合はシャローで十分、mutable が多い場合はディープコピーを検討。
まずは 絶対にJavaプログラマーになりたい人へ。 を読み、さらに実務スキルや転職を目指すなら サイゼントアカデミー を活用してください。


コメント