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 オブジェクト)もすべて新しく作ります。 コピー後の変更は元のリストにまったく影響しません。
サンプルコード
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()で配列をコピーしたとき、元の配列を変更してもコピー先には影響しないこと