web-dev-qa-db-ja.com

clone()vs copy constructor vs factory method?

Clone()をJavaで実装することについて簡単にGoogleで調べてみたところ、 http://www.javapractices.com/topic/TopicAction.do?Id=71

次のコメントがあります。

コピーコンストラクターと静的ファクトリメソッドは、クローンの代替手段を提供し、実装がはるかに簡単です。

私がやりたいのは、深いコピーを作成することです。 clone()を実装することは非常に理にかなっているように見えますが、この非常にグーグルにランク付けされた記事は私を少し怖がらせます。

私が気づいた問題は次のとおりです。

コピーコンストラクターはジェネリックでは機能しません。

コンパイルできない擬似コードを次に示します。

public class MyClass<T>{
   ..
   public void copyData(T data){
       T copy=new T(data);//This isn't going to work.    
   }
   ..
}

サンプル1:ジェネリッククラスでコピーコンストラクターを使用します。

ファクトリメソッドには標準名がありません。

再利用可能なコード用のインターフェイスがあるのはとてもいいことです。

public class MyClass<T>{
    ..
    public void copyData(T data){
        T copy=data.clone();//Throws an exception if the input was not cloneable
    }
    ..
}

サンプル2:汎用クラスでclone()を使用します。

クローンは静的メソッドではないことに気付きましたが、保護されたすべてのフィールドのディープコピーを作成する必要はありませんか? clone()を実装する場合、クローン化できないサブクラスで例外をスローするための余分な労力は私にはささいなようです。

何か不足していますか?どんな洞察もいただければ幸いです。

76
User1

基本的に、 クローンが壊れています 。ジェネリックで簡単に機能するものはありません。このようなものがある場合(要点を理解するために短縮):

public class SomeClass<T extends Copyable> {


    public T copy(T object) {
        return (T) object.copy();
    }
}

interface Copyable {
    Copyable copy();
}

その後、コンパイラの警告が表示されると、ジョブを完了できます。ジェネリックは実行時に消去されるため、コピーを行うものには、コンパイラ警告が生成され、キャストが生成されます。 この場合、回避することはできません。。場合によっては回避できます(ありがとう、kb304)が、すべてではありません。インターフェイスを実装するサブクラスまたは不明なクラスをサポートする必要がある場合を考えます(必ずしも同じクラスを生成するとは限らないコピー可能ファイルのコレクションを反復処理した場合など)。

49
Yishai

Builderパターンもあります。詳細については、有効なJavaを参照してください。

あなたの評価がわかりません。コピーコンストラクターでは、型を完全に認識していますが、なぜジェネリックを使用する必要があるのですか?

public class C {
   public int value;
   public C() { }
   public C(C other) {
     value = other.value;
   }
}

最近、同様の質問がありました here

public class G<T> {
   public T value;
   public G() { }
   public G(G<? extends T> other) {
     value = other.value;
   }
}

実行可能なサンプル:

public class GenTest {
    public interface Copyable<T> {
        T copy();
    }
    public static <T extends Copyable<T>> T copy(T object) {
        return object.copy();
    }
    public static class G<T> implements Copyable<G<T>> {
        public T value;
        public G() {
        }
        public G(G<? extends T> other) {
            value = other.value;
        }
        @Override
        public G<T> copy() {
            return new G<T>(this);
        }
    }
    public static void main(String[] args) {
        G<Integer> g = new G<Integer>();
        g.value = 1;
        G<Integer> f = g.copy();
        g.value = 2;
        G<Integer> h = copy(g);
        g.value = 3;
        System.out.printf("f: %s%n", f.value);
        System.out.printf("g: %s%n", g.value);
        System.out.printf("h: %s%n", h.value);
    }
}
25
akarnokd

Javaには、C++と同じ意味でのコピーコンストラクターはありません。

引数と同じ型のオブジェクトを取るコンストラクターを使用できますが、これをサポートするクラスはほとんどありません。 (クローン対応をサポートする数未満)

一般的なクローンの場合、クラスの新しいインスタンスを作成し、リフレクション(実際にはリフレクションのようなものですがより高速なもの)を使用して元のフィールド(浅いコピー)をコピーするヘルパーメソッドがあります

ディープコピーの場合、簡単なアプローチはオブジェクトをシリアル化し、オブジェクトをシリアル化解除することです。

ところで:私の提案は、不変オブジェクトを使用することです。そうすれば、それらを複製する必要はありません。 ;)

8
Peter Lawrey

Yishaiの回答は改善される可能性があるため、次のコードでは警告を表示できません。

public class SomeClass<T extends Copyable<T>> {

    public T copy(T object) {
        return object.copy();
    }
}

interface Copyable<T> {
    T copy();
}

このように、Copyableインターフェースを実装する必要があるクラスは次のようにする必要があります。

public class MyClass implements Copyable<MyClass> {

    @Override
    public MyClass copy() {
        // copy implementation
        ...
    }

}
5
Alepac

以下は、多くの開発者がObject.clone()を使用しないための短所です。

  1. Object.clone()メソッドを使用するには、Cloneableインターフェイスを実装し、clone()メソッドを定義してCloneNotSupportedExceptionを処理し、最後にを呼び出すなど、多くの構文をコードに追加する必要があります。 Object.clone()そしてオブジェクトにキャストします。
  2. Cloneableインターフェイスにはclone()メソッドがありません。これはマーカーインターフェイスであり、メソッドはありません。それでも、clone()オブジェクト。
  3. Object.clone()は保護されているため、独自のclone()を提供し、それから間接的にObject.clone()を呼び出す必要があります。
  4. Object.clone()はコンストラクターを呼び出さないため、オブジェクトの構築を制御することはできません。
  5. 子クラスでclone()メソッドを書いている場合Personの場合、そのすべてのスーパークラスはclone()メソッドを定義するか、別の親クラスから継承する必要があります。そうしないと、super.clone()チェーンが失敗します。
  6. Object.clone()は浅いコピーのみをサポートしているため、新しく複製されたオブジェクトの参照フィールドには、元のオブジェクトのフィールドが保持していたオブジェクトが保持されます。これを克服するためには、クラスが参照しているすべてのクラスにclone()を実装し、以下の例のようにclone()メソッドでそれらを個別に複製する必要があります。
  7. 最終フィールドはコンストラクターを介してのみ変更できるため、Object.clone()の最終フィールドを操作することはできません。この場合、すべてのPersonオブジェクトをIDで一意にしたい場合、Object.clone()はコンストラクターを呼び出さないため、Object.clone()を使用すると重複オブジェクトを取得します。最後のidフィールドはPerson.clone()から変更できません。

コピーコンストラクターはObject.clone()よりも優れています

  1. インターフェースの実装や例外のスローを強制しないでください。必要な場合は確実に実行できます。
  2. キャストは不要です。
  3. 未知のオブジェクト作成メカニズムに依存する必要はありません。
  4. 親クラスに契約に従うことや何かを実装することを要求しないでください。
  5. 最終フィールドの変更を許可します。
  6. オブジェクトの作成を完全に制御できるようにし、その中に初期化ロジックを書くことができます。

Java Cloning-Copy Constructor vs Cloning の詳細を読む

2
Naresh Joshi

あなたのために働くかもしれない1つのパターンは、Beanレベルのコピーです。基本的に、引数なしのコンストラクタを使用し、さまざまなセッターを呼び出してデータを提供します。さまざまなBeanプロパティライブラリを使用して、プロパティを比較的簡単に設定することもできます。これはclone()を実行するのと同じではありませんが、多くの実用的な目的のためには問題ありません。

clone()のすべての癖を100%認識していない場合は、それを避けてください。 clone()が壊れているとは言いません。私は言うだろう:あなたがそれがあなたの最良の選択肢であることを完全に確信しているときだけ、それを使う。コピーコンストラクター(またはファクトリーメソッド、それは本当に重要ではない)は書くのが簡単で(おそらく長くても簡単です)、コピーしたいものだけをコピーし、コピーしたい方法をコピーします。必要に応じてトリミングできます。

さらに、コピーコンストラクター/ファクトリーメソッドを呼び出したときに何が起こるかを簡単にデバッグできます。

そして、clone()は、参照(たとえばCollectionへの)だけがコピーされることを意味すると仮定して、オブジェクトの「深い」コピーをすぐに作成しません。しかし、ここでディープとシャローの詳細をお読みください: ディープコピー、シャローコピー、クローン

0

Cloneableインターフェイスは役に立たないという意味で壊れていますが、クローンはうまく機能し、大きなオブジェクト(8フィールド以上)のパフォーマンスが向上する可能性がありますが、エスケープ分析は失敗します。ほとんどの場合、コピーコンストラクターを使用することをお勧めします。配列でのクローンの使用は、長さが同じであることが保証されているため、Arrays.copyOfよりも高速です。

詳細はこちら https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

0
user3996996

通常、clone()は、保護されたコピーコンストラクターと連携して動作します。 clone()は、コンストラクターとは異なり、仮想化できるため、これが行われます。

Derived for a super class Baseのクラスボディには、

class Derived extends Base {
}

そのため、最も単純な方法で、これにclone()を使用してバーチャルコピーコンストラクターを追加します。 (C++では、Joshiはバーチャルコピーコンストラクターとしてcloneを推奨しています。)

protected Derived() {
    super();
}

protected Object clone() throws CloneNotSupportedException {
    return new Derived();
}

推奨されているようにsuper.clone()を呼び出し、これらのメンバーをクラスに追加する必要がある場合、より複雑になります。これを試すことができます

final String name;
Address address;

/// This protected copy constructor - only constructs the object from super-class and
/// sets the final in the object for the derived class.
protected Derived(Base base, String name) {
   super(base);
   this.name = name;
}

protected Object clone() throws CloneNotSupportedException {
    Derived that = new Derived(super.clone(), this.name);
    that.address = (Address) this.address.clone();
}

今、実行する場合、あなたは

Base base = (Base) new Derived("name");

そしてあなたはそれから

Base clone = (Base) base.clone();

これにより、Derivedクラス(上記のクラス)のclone()が呼び出され、super.clone()が呼び出されます。これは実装される場合とされない場合がありますが、呼び出すことをお勧めします。次に、実装はsuper.clone()の出力を、Baseを取得する保護されたコピーコンストラクターに渡します。このコンストラクターには、最終メンバーを渡します。

次に、そのコピーコンストラクターがスーパークラスのコピーコンストラクターを呼び出し(スーパークラスにあることがわかっている場合)、ファイナルを設定します。

Clone()メソッドに戻ったら、最終メンバー以外を設定します。

賢明な読者は、Baseにコピーコンストラクターがある場合、super.clone()によって呼び出され、保護されたコンストラクターでスーパーコンストラクターを呼び出すと再び呼び出されることに気付くでしょう。スーパーコピーコンストラクターを2回。うまくいけば、それがリソースをロックしている場合、それを知っているでしょう。

0
Bonaparte