スーパークラスを持たないオブジェクトの1つにディープクローンを実装する必要があります。
スーパークラス(CloneNotSupportedException
)によってスローされたチェック済みObject
を処理する最良の方法は何ですか?
同僚から、次の方法で処理するように勧められました。
@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}
// Deep clone member fields here
return foo;
}
これは私にとっては良い解決策のように思えますが、他の洞察を含めることができるかどうかを確認するためにStackOverflowコミュニティに投げ出したかったのです。ありがとう!
絶対にclone
を使用する必要がありますか?ほとんどの人は、Javaのclone
が壊れていることに同意します。
Josh Bloch on Design-Copy Constructor vs Cloning
私の本でクローン作成に関する項目を読んだ場合、特に行間を読んだ場合、
clone
が非常に壊れていると思います。 [...]Cloneable
が壊れているのは残念ですが、それは起こります。
彼の本でこのトピックに関する詳細な議論を読むことができます。効果的なJava第2版、項目11:clone
を慎重にオーバーライドします。代わりに、コピーコンストラクターまたはコピーファクトリを使用することをお勧めします。
彼は続けて、あなたが必要だと思うなら、clone
をどのように実装すべきかについてのページのページを書きました。しかし、彼はこれで閉じました:
こうした複雑さは本当に必要なのでしょうか?まれに。
Cloneable
を実装するクラスを拡張する場合、適切に動作するclone
メソッドを実装する以外に選択肢はほとんどありません。それ以外の場合、オブジェクトのコピーの代替手段を提供するか、単に機能を提供しない方が良いでしょう。
強調されたのは私のものではなく、彼でした。
clone
を実装する以外に選択肢がほとんどないことを明確にしたため、この場合にできることは次のとおりです。必ずMyObject extends Java.lang.Object implements Java.lang.Cloneable
を確認してください。その場合は、がCloneNotSupportedException
をキャッチしないことを保証できます。一部の人が示唆しているようにAssertionError
を投げるのは理にかなっているようですが、この特定の場合にcatchブロックが入力されない理由を説明するコメントを追加することもできます。
あるいは、他の人も示唆しているように、おそらくsuper.clone
を呼び出さずにclone
を実装できます。
コピーコンストラクタを実装する方が簡単な場合があります。
public MyObject (MyObject toClone) {
}
CloneNotSupportedException
を処理する手間を省き、final
フィールドで動作し、返される型を心配する必要がありません。
コードの動作方法は、コードを記述するための「標準的な」方法にかなり近いです。ただし、キャッチ内にAssertionError
をスローします。その行に決して到達してはならないことを通知します。
catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
CloneNotSupportedException
がスローされるケースは2つあります。
Cloneable
を実装していません(実際の複製は最終的にObject
のcloneメソッドに従うと仮定します)。このメソッドを記述しているクラスがCloneable
を実装している場合、これは起こりません(サブクラスは適切に継承するため)。Cloneable
である場合にサブクラスのクローン化を防ぐための推奨される方法です。super.clone()
を呼び出すサブクラスから呼び出された場合でも、try
ブロックでスーパークラスのメソッドを直接呼び出しているため、後者のケースはクラスで発生することはありません。 Cloneable
を実装する必要があります。
基本的に、エラーを確実にログに記録する必要がありますが、この特定のインスタンスでは、クラスの定義を台無しにした場合にのみ発生します。したがって、NullPointerException
(または同様の)のチェックバージョンのように処理します。コードが機能している場合はスローされません。
他の状況では、この不測の事態に備える必要があります-与えられたオブジェクトisがクローン可能であるという保証はありません。オブジェクト、別のクローン作成戦略、たとえば、serialize-deserializeを使用し、メソッドがcloneableなどによるパラメーターを必要とする場合はIllegalParameterException
をスローします。
編集:全体的にはそうですが、clone()
は本当に正しく実装するのが難しく、呼び出し側が戻り値が必要なものになるかどうかを知るのは難しいです。深いクローンと浅いクローン。多くの場合、全体を完全に避けて別のメカニズムを使用することをお勧めします。
serialization を使用して、ディープコピーを作成します。これは最速の解決策ではありませんが、タイプに依存しません。
次のように、プロテクトコピーコンストラクターを実装できます。
/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
// etc
}
public MyObject clone() {
return new MyObject(this);
}
public class MyObject implements Cloneable, Serializable{
@Override
@SuppressWarnings(value = "unchecked")
protected MyObject clone(){
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bOs = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bOs);
oos.writeObject(this);
ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
return (MyObject)ois.readObject();
} catch (Exception e) {
//Some seriouse error :< //
return null;
}finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
}
if (ois != null)
try {
ois.close();
} catch (IOException e) {
}
}
}
}
JavaのCloneableの実装が壊れているからといって、独自のものを作成できないわけではありません。
OPの本当の目的がディープクローンを作成することだった場合、次のようなインターフェイスを作成できると思います。
public interface Cloneable<T> {
public T getClone();
}
次に、前述のプロトタイプコンストラクタを使用して実装します。
public class AClass implements Cloneable<AClass> {
private int value;
public AClass(int value) {
this.vaue = value;
}
protected AClass(AClass p) {
this(p.getValue());
}
public int getValue() {
return value;
}
public AClass getClone() {
return new AClass(this);
}
}
およびAClassオブジェクトフィールドを持つ別のクラス:
public class BClass implements Cloneable<BClass> {
private int value;
private AClass a;
public BClass(int value, AClass a) {
this.value = value;
this.a = a;
}
protected BClass(BClass p) {
this(p.getValue(), p.getA().getClone());
}
public int getValue() {
return value;
}
public AClass getA() {
return a;
}
public BClass getClone() {
return new BClass(this);
}
}
この方法で、@ SuppressWarningsやその他のギミックなコードを必要とせずに、クラスBClassのオブジェクトを簡単に深くクローンできます。
ここでの回答のほとんどが有効である限り、あなたのソリューションは実際のJava API開発者のやり方でもあることを伝える必要があります。 (Josh BlochまたはNeal Gafterのいずれか)
以下は、openJDK、ArrayListクラスからの抜粋です。
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
お気づきのとおり、他の人が言及したように、CloneNotSupportedException
インターフェースを実装すると宣言した場合、Cloneable
はスローされる可能性がほとんどありません。
Also、オーバーライドされたメソッドで新しい操作を行わない場合、メソッドをオーバーライドする必要はありません。オブジェクトに対して追加の操作を行う必要がある場合、またはオブジェクトをパブリックにする必要がある場合にのみ、オーバーライドする必要があります。
最終的には、それを回避し、他の方法を使用して行うことが依然として最善です。