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


114

スーパークラスを持たないオブジェクトの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コミュニティにそれを投げ入れて、他に含めることができる洞察があるかどうかを確認したいと思いました。ありがとう!


6
親クラスが実装していることがわかっている場合は、単なるプレーンではなくをCloneableスローする方が表現力があります。AssertionErrorError
Andrew Duffy、

編集:私はむしろclone()を使用しませんが、プロジェクトはすでにそれに基づいており、この時点ですべての参照をリファクタリングする価値はありません。
Cuga

弾丸を後で噛むよりも、今すぐ噛む方がよい(まあ、プロダクションを攻撃しようとしているのでない限り)。
トム・ホーティン-タックライン

これが完了するまで、スケジュールには1か月半残っています。時間を正当化することはできません。
Cuga

この解決策は完璧だと思います。そして、クローンの使用について気を悪くしないでください。トランスポートオブジェクトとして使用されるクラスがあり、それが文字列や列挙型などの権限と不変要素のみを含んでいる場合、クローン以外のすべてのものは独断的な理由で時間の完全な無駄になります。クローンが何をするか、何をしないかを覚えておいてください!(ディープクローニングなし)
TomWolk、2015

回答:


125

絶対に使用する必要がありますcloneか?ほとんどの人は、Java cloneが壊れていることに同意します。

Josh Bloch on Design-コピーコンストラクターと複製

私の本でクローンについての項目を読んだ場合、特に行間を読んだ場合、私cloneは深く壊れていると思います。[...] Cloneable壊れているのは残念ですが、それは起こります。

このトピックの詳細については、彼の著書「Effective Java 2nd Edition、Item 11:Override clone思慮深く」を参照してください。代わりに、コピーコンストラクターまたはコピーファクトリーを使用することをお勧めします。

彼は続けて、必要に応じて、どのように実装すべきかについて、何ページにもわたって書きましたclone。しかし、彼はこれで閉じました:

このすべての複雑さが本当に必要ですか?めったにありません。を実装するクラスを拡張する場合Cloneable、適切に動作するcloneメソッドを実装する以外に選択肢はほとんどありません。それ以外の場合は、オブジェクトのコピーの代替手段を提供するか、単に機能を提供しない方がよいでしょう

強調は私のものではなく彼のものでした。


実装する以外に選択肢はほとんどないことを明確にしたのでclone、この場合にできることは次のとおりですMyObject extends java.lang.Object implements java.lang.Cloneable。その場合は、絶対にをキャッチしないことを保証できますCloneNotSupportedException。投げるAssertionError一部が示唆されているようすることは合理的なようだが、あなたはまた、catchブロックが入力されることはありません理由を説明するコメントを追加することができ、この特定のケースでは


または、他の人も示唆しているように、をclone呼び出さずに実装することもできますsuper.clone


1
残念ながら、プロジェクトはすでにcloneメソッドを使用して作成されていますが、それ以外の場合は絶対に使用しません。Javaのcloneの実装がfakaktaであることに、私は完全に同意します。
Cuga

5
クラスとそのすべてのスーパークラスsuper.clone()がクローンメソッド内で呼び出す場合、サブクラスは通常clone()、内容を複製する必要がある新しいフィールドを追加する場合にのみオーバーライドする必要があります。スーパークラスがではnewなくを使用する場合super.clone()、すべてのサブクラスはclone()、新しいフィールドを追加するかどうかをオーバーライドする必要があります。
スーパーキャット2012年

56

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

public MyObject (MyObject toClone) {
}

処理の手間が省けCloneNotSupportedExceptionfinalフィールドを操作できるため、返されるタイプを気にする必要がありません。


11

コードが機能する方法は、コードを記述する「標準的な」方法にかなり近いです。でも私AssertionErrorはキャッチ内で投げます。その行に到達してはならないことを示します。

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

これが悪い考えである理由については、@ polygenelubricantsの回答を参照してください。
カールリヒター2016

@KarlRichter私は彼らの答えを読みました。Cloneable効果的なJavaで説明されているように、それが一般的に壊れたアイデアであること以外は、それが悪いアイデアだとは言いません。OPはすでに使用する必要があることを表明していますCloneable。だから私は、おそらくそれを完全に削除することを除いて、私の答えを他にどのように改善できるかわかりません。
Chris Jester-Young

9

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

  1. 複製されているクラスは実装されていませんCloneable(実際の複製は最終的にObjectのcloneメソッドに委ねられると想定しています)。このメソッドをimplements Cloneableで作成しているクラスの場合、これは発生しません(サブクラスが適切に継承するため)。
  2. 例外は実装によって明示的にスローされます。これは、スーパークラスがであるときにサブクラスでのクローン性を防止するための推奨される方法ですCloneable

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

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


他の状況では、このような事態に備える必要があります-特定のオブジェクト複製可能である保証はないため、例外をキャッチするとき、この条件に応じて適切なアクションを実行する必要があります(既存のオブジェクトで続行し、代替の複製戦略をとります)たとえば、serialize- IllegalParameterExceptiondeserialize、メソッドでcloneableなどのパラメーターが必要な場合は、スローします。

編集:全体的に私はそうですが、clone()正確に実装することは本当に難しく、呼び出し元が戻り値が必要なものかどうかを知るのは難しいので、深いクローンと浅いクローンを検討する場合は二重に注意してください。全体を完全に回避し、別のメカニズムを使用する方がよい場合がよくあります。


オブジェクトがパブリッククローンメソッドを公開している場合、それをサポートしていない派生オブジェクトは、リスコフの置換原則に違反します。クローン作成メソッドが保護されている場合は、適切なタイプを返すメソッド以外のものでシャドウイングして、サブクラスがを呼び出そうとしないようにする方がよいと思いますsuper.clone()
スーパーキャット2012


3

次のように保護されたコピーコンストラクタを実装できます。

/* 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
これはほとんどの場合機能しません。メソッドがなければ、clone()によって返されたオブジェクトを呼び出すことはできません。getMySecondMember()public clone
polygenelubricants 2010

明らかに、クローンを作成したいすべてのオブジェクトにこのパターンを実装するか、各メンバーをディープクローンする別の方法を見つける必要があります。
ドルフ

はい、それは必要な要件です。
polygenelubricants

1

ここでの答えのほとんどは有効ですが、あなたのソリューションは実際の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インターフェースを実装することを宣言したならば、投げられる機会はほとんどありません。

また、オーバーライドされたメソッドで新しいことを何も行わない場合は、メソッドをオーバーライドする必要はありません。オブジェクトに対して追加の操作を実行する必要がある場合、またはオブジェクトをパブリックにする必要がある場合にのみ、オーバーライドする必要があります。

結局のところ、それを回避し、他の方法でそれを行うのが依然として最善です。


0
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) {

                }
        }
    }
}

これで、ファイルシステムに書き込み、オブジェクトを読み取ったことになります。さて、これはクローンを処理するための最良の方法ですか?SOコミュニティの誰もがこのアプローチについてコメントできますか?これは、クローニングとシリアライゼーションを不必要に結び付けていると思います。2つのまったく異なる概念です。私は他の人がこれについて何を言わなければならないか見るのを待ちます。
Saurabh Patil、2014年

2
これはファイルシステムではなく、メインメモリ(ByteArrayOutputStream)にあります。深くネストされたオブジェクトの場合、そのソリューションはうまく機能します。特にユニットテストなどで頻繁に必要としない場合。パフォーマンスが主な目標ではない場合。
AlexWien 2015年

0

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クラスのオブジェクトを簡単にディープクローンできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.