web-dev-qa-db-ja.com

クローンメソッドを適切にオーバーライドする方法は?

スーパークラスを持たないオブジェクトの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コミュニティに投げ出したかったのです。ありがとう!

108
Cuga

絶対に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を実装できます。

119

コピーコンストラクタを実装する方が簡単な場合があります。

public MyObject (MyObject toClone) {
}

CloneNotSupportedExceptionを処理する手間を省き、finalフィールドで動作し、返される型を心配する必要がありません。

55
Aaron Digulla

コードの動作方法は、コードを記述するための「標準的な」方法にかなり近いです。ただし、キャッチ内にAssertionErrorをスローします。その行に決して到達してはならないことを通知します。

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}
11

CloneNotSupportedExceptionがスローされるケースは2つあります。

  1. 複製されるクラスはCloneableを実装していません(実際の複製は最終的にObjectのc​​loneメソッドに従うと仮定します)。このメソッドを記述しているクラスがCloneableを実装している場合、これは起こりません(サブクラスは適切に継承するため)。
  2. 実装によって例外が明示的にスローされます-これは、スーパークラスがCloneableである場合にサブクラスのクローン化を防ぐための推奨される方法です。

super.clone()を呼び出すサブクラスから呼び出された場合でも、tryブロックでスーパークラスのメソッドを直接呼び出しているため、後者のケースはクラスで発生することはありません。 Cloneableを実装する必要があります。

基本的に、エラーを確実にログに記録する必要がありますが、この特定のインスタンスでは、クラスの定義を台無しにした場合にのみ発生します。したがって、NullPointerException(または同様の)のチェックバージョンのように処理します。コードが機能している場合はスローされません。


他の状況では、この不測の事態に備える必要があります-与えられたオブジェクトisがクローン可能であるという保証はありません。オブジェクト、別のクローン作成戦略、たとえば、serialize-deserializeを使用し、メソッドがcloneableなどによるパラメーターを必要とする場合はIllegalParameterExceptionをスローします。

編集:全体的にはそうですが、clone()は本当に正しく実装するのが難しく、呼び出し側が戻り値が必要なものになるかどうかを知るのは難しいです。深いクローンと浅いクローン。多くの場合、全体を完全に避けて別のメカニズムを使用することをお勧めします。

9
Andrzej Doyle

serialization を使用して、ディープコピーを作成します。これは最速の解決策ではありませんが、タイプに依存しません。

5
Michał Ziober

次のように、プロテクトコピーコンストラクターを実装できます。

/* 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);
}
3
Dolph
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) {

                }
        }
    }
}
0
Kvant_hv

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のオブジェクトを簡単に深くクローンできます。

0
Francesco Rogo

ここでの回答のほとんどが有効である限り、あなたのソリューションは実際のJava AP​​I開発者のやり方でもあることを伝える必要があります。 (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、オーバーライドされたメソッドで新しい操作を行わない場合、メソッドをオーバーライドする必要はありません。オブジェクトに対して追加の操作を行う必要がある場合、またはオブジェクトをパブリックにする必要がある場合にのみ、オーバーライドする必要があります。

最終的には、それを回避し、他の方法を使用して行うことが依然として最善です。

0
Haggra