Javaにおけるオブジェクトのコピーとは、既存のインスタンスと同じ状態を持つ新しいオブジェクトを作成するプロセスです。オブジェクトを手動で再作成せずに複製する必要性が生じたのは、Javaの初期の発展においてでした。Java自体はオブジェクトをコピーするためのユニバーサルな手段を提供していないため、clone()という契約とコピーコンストラクタパターンを使用します。
clone()メソッドは、オブジェクトをクローンするための標準的なメカニズムを提供するためにCloneableインターフェースに導入されましたが、その実装には多くのニュアンスがあり、しばしばバグを引き起こします。
主な問題は、「箱から出して真の深いコピー」が存在しないことと、浅いクローンを行った場合の予測不可能な動作が起こる可能性です。コピーコンストラクタは浅いコピーと深いコピーの両方を実現できますが、明示的な実装が必要です。
複雑なオブジェクトを安全かつ正確にコピーするには、コピーコンストラクタまたはファクトリメソッドの使用が好ましいです。一方で、clone()は、この契約に適している場合にのみ使用するべきです。
コード例:
// clone()による浅いクローン public class Address implements Cloneable { String city; public Address(String city) { this.city = city; } @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } } public class Person implements Cloneable { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } @Override public Person clone() throws CloneNotSupportedException { Person copy = (Person) super.clone(); copy.address = address.clone(); // アドレスの深いコピー return copy; } } // コピーコンストラクタ public class Person { String name; Address address; public Person(Person other) { this.name = other.name; this.address = new Address(other.address.city); } }
重要な特徴:
clone()はCloneableインターフェースの実装と、cloneメソッドのオーバーライドを要求します。質問 1: クラスがCloneableを実装していない場合、clone()を呼び出すとどうなりますか?
clone()は、オブジェクトがCloneableインターフェースを実装していない場合、CloneNotSupportedExceptionをスローします。
質問 2: clone()によるクローン作成はデフォルトで深いですか?
いいえ、デフォルトでは浅いクローン作成のみが実装されており、すべてのオブジェクトへの参照がそのままコピーされます。
質問 3: 深いクローンを行うためにコピーコンストラクタなしで済ませられますか?
いいえ、プロセスを制御したい場合は、コンストラクタを通じてネストされたオブジェクトのコピーを明示的に指定する必要があります。
開発者がList<Product>フィールドを持つOrderクラスで標準のclone()を使用した結果、コピーされた製品リストを変更すると、オリジナルの製品も変更され、バグが発生しました。
利点:
欠点:
会社ではすべてのドメインエンティティにコピーコンストラクタを実装し、複雑なネストされたオブジェクトのクローン作成プロセスをテストでカバーしました。
利点:
欠点: