java-recipes

ホーム コピーパターン › Cop-01

Cop-01: Shallow Copy vs Deep Copy の違い

Java でオブジェクトをコピーするとき、「参照コピー」「Shallow Copy(浅いコピー)」「Deep Copy(深いコピー)」の 3種類があります。それぞれの違いと正しい使い方をList<Person> を例に解説します。

3種類のコピーの違い

Java でオブジェクトを別の変数に代入するだけでは、実際にはコピーされていません。 変数には「参照(メモリ上の場所を指すポインタ)」が格納されているため、 代入しても同じオブジェクトを2つの変数で共有している状態になります。

3種類の比較

参照コピー(コピーではない)

List<Person> copy = original; のような代入は コピーではなく、同じリストへの参照を共有します。 どちらの変数を通じて変更しても、同じオブジェクトが変更されます。

Shallow Copy(浅いコピー)

new ArrayList<>(original) はリスト自体は新しく作りますが、 リストの中に入っている要素(Person オブジェクト)は同じ参照を共有します。 リストへの要素追加・削除は独立しますが、要素の内容変更は元のリストにも影響します。

Deep Copy(深いコピー)

リスト自体だけでなく、リストの中に入っている要素(Person オブジェクト)もすべて新しく作ります。 コピー後の変更は元のリストにまったく影響しません。

サンプルコード

CopyPatternSample.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CopyPatternSample {

    static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public int getAge() { return age; }

        // ディープコピー用メソッド
        public Person deepCopy() {
            return new Person(this.name, this.age);
        }

        @Override
        public String toString() {
            return "Person{name=" + name + ", age=" + age + "}";
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 参照コピー(コピーになっていない) ===");
        List<Person> original = new ArrayList<>();
        original.add(new Person("田中太郎", 25));
        original.add(new Person("山田花子", 30));

        List<Person> sameRef = original; // 同じリスト
        sameRef.get(0).setName("変更された名前");
        System.out.println("original[0]: " + original.get(0)); // 変更されている

        System.out.println("\n=== Shallow Copy(浅いコピー) ===");
        List<Person> original2 = new ArrayList<>(Arrays.asList(
            new Person("田中太郎", 25),
            new Person("山田花子", 30)
        ));

        List<Person> shallowCopy = new ArrayList<>(original2); // リストは新規

        // リストへの追加は影響しない
        shallowCopy.add(new Person("新しい人", 20));
        System.out.println("original2 サイズ: " + original2.size()); // 2(影響なし)

        // 要素の変更は影響する(参照共有)
        shallowCopy.get(0).setName("変更された");
        System.out.println("original2[0]: " + original2.get(0)); // 変更されている

        System.out.println("\n=== Deep Copy(深いコピー) ===");
        List<Person> original3 = new ArrayList<>(Arrays.asList(
            new Person("田中太郎", 25),
            new Person("山田花子", 30)
        ));

        List<Person> deepCopy = new ArrayList<>();
        for (Person p : original3) {
            deepCopy.add(p.deepCopy()); // 各要素も新規コピー
        }

        deepCopy.get(0).setName("変更しても");
        System.out.println("original3[0]: " + original3.get(0)); // 変更されていない

        System.out.println("\n=== int[] の配列コピー ===");
        int[] arr = {1, 2, 3, 4, 5};
        int[] shallowArr = arr;       // 参照コピー
        int[] copiedArr = Arrays.copyOf(arr, arr.length); // 配列コピー

        arr[0] = 99;
        System.out.println("shallowArr[0]: " + shallowArr[0]); // 99(影響あり)
        System.out.println("copiedArr[0]: " + copiedArr[0]);   // 1(影響なし)
    }
}

Java 8 では deepCopy() メソッドを自前で実装するか、コピーコンストラクタを用意してディープコピーを行います。for ループで各要素をコピーするのが基本的なパターンです。

よくあるミス・注意点

⚠️ new ArrayList<>(original) でコピーしたつもりが要素は共有されている

new ArrayList<>(original) はリストのコンテナ(箱)だけを新しく作ります。 リストの中身(要素オブジェクト)は元のリストと同じ参照を共有しています。 そのため、コピー後に要素の内容(フィールド)を変更すると、元のリストの要素も変わってしまいます。 要素も含めて完全にコピーしたい場合は、各要素を個別にコピーするディープコピーが必要です。

⚠️ Cloneable インターフェースによる clone() は推奨されない

Java には Cloneable インターフェースとclone() メソッドがありますが、 設計上の問題点が多く現在は推奨されていません。 コピーコンストラクタ(new Person(other))や 専用のコピーメソッド(deepCopy())を使うほうが明示的で安全です。

⚠️ ネストされたオブジェクトは再帰的にコピーが必要

Person クラスがさらに Address クラスを持つような場合、 Person だけコピーしても Address は共有されたままです。 完全なディープコピーを実現するには、ネストされたオブジェクトもすべてコピーする必要があります。 オブジェクトの階層が深い場合は Jackson などのライブラリによるシリアライズ・デシリアライズでディープコピーを行う方法もあります。

テストする観点

  • 参照コピー後に要素を変更したとき、元のリストの要素も変更されること
  • Shallow Copy 後にリストへ要素を追加しても、元のリストのサイズが変わらないこと
  • Shallow Copy 後に要素の内容(フィールド)を変更したとき、元のリストの要素も変更されること
  • Deep Copy 後に要素の内容(フィールド)を変更しても、元のリストの要素は変更されないこと
  • 空のリスト(要素数 0)を Shallow Copy / Deep Copy しても例外が発生しないこと(境界値)
  • 要素が 1 件のリストに対して Shallow Copy / Deep Copy が正しく動作すること(境界値)
  • Arrays.copyOf() で配列をコピーしたとき、元の配列を変更してもコピー先には影響しないこと

GitHub でソースコードを見る →