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()を実装する場合、クローン化できないサブクラスで例外をスローするための余分な労力は私にはささいなようです。
何か不足していますか?どんな洞察もいただければ幸いです。
基本的に、 クローンが壊れています 。ジェネリックで簡単に機能するものはありません。このようなものがある場合(要点を理解するために短縮):
public class SomeClass<T extends Copyable> {
public T copy(T object) {
return (T) object.copy();
}
}
interface Copyable {
Copyable copy();
}
その後、コンパイラの警告が表示されると、ジョブを完了できます。ジェネリックは実行時に消去されるため、コピーを行うものには、コンパイラ警告が生成され、キャストが生成されます。 この場合、回避することはできません。。場合によっては回避できます(ありがとう、kb304)が、すべてではありません。インターフェイスを実装するサブクラスまたは不明なクラスをサポートする必要がある場合を考えます(必ずしも同じクラスを生成するとは限らないコピー可能ファイルのコレクションを反復処理した場合など)。
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);
}
}
Javaには、C++と同じ意味でのコピーコンストラクターはありません。
引数と同じ型のオブジェクトを取るコンストラクターを使用できますが、これをサポートするクラスはほとんどありません。 (クローン対応をサポートする数未満)
一般的なクローンの場合、クラスの新しいインスタンスを作成し、リフレクション(実際にはリフレクションのようなものですがより高速なもの)を使用して元のフィールド(浅いコピー)をコピーするヘルパーメソッドがあります
ディープコピーの場合、簡単なアプローチはオブジェクトをシリアル化し、オブジェクトをシリアル化解除することです。
ところで:私の提案は、不変オブジェクトを使用することです。そうすれば、それらを複製する必要はありません。 ;)
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
...
}
}
以下は、多くの開発者がObject.clone()
を使用しないための短所です。
Object.clone()
メソッドを使用するには、Cloneable
インターフェイスを実装し、clone()
メソッドを定義してCloneNotSupportedException
を処理し、最後にを呼び出すなど、多くの構文をコードに追加する必要があります。 Object.clone()
そしてオブジェクトにキャストします。Cloneable
インターフェイスにはclone()
メソッドがありません。これはマーカーインターフェイスであり、メソッドはありません。それでも、clone()
オブジェクト。Object.clone()
は保護されているため、独自のclone()
を提供し、それから間接的にObject.clone()
を呼び出す必要があります。Object.clone()
はコンストラクターを呼び出さないため、オブジェクトの構築を制御することはできません。clone()
メソッドを書いている場合Person
の場合、そのすべてのスーパークラスはclone()
メソッドを定義するか、別の親クラスから継承する必要があります。そうしないと、super.clone()
チェーンが失敗します。Object.clone()
は浅いコピーのみをサポートしているため、新しく複製されたオブジェクトの参照フィールドには、元のオブジェクトのフィールドが保持していたオブジェクトが保持されます。これを克服するためには、クラスが参照しているすべてのクラスにclone()
を実装し、以下の例のようにclone()
メソッドでそれらを個別に複製する必要があります。Object.clone()
の最終フィールドを操作することはできません。この場合、すべてのPerson
オブジェクトをIDで一意にしたい場合、Object.clone()
はコンストラクターを呼び出さないため、Object.clone()
を使用すると重複オブジェクトを取得します。最後のid
フィールドはPerson.clone()
から変更できません。コピーコンストラクターはObject.clone()
よりも優れています
あなたのために働くかもしれない1つのパターンは、Beanレベルのコピーです。基本的に、引数なしのコンストラクタを使用し、さまざまなセッターを呼び出してデータを提供します。さまざまなBeanプロパティライブラリを使用して、プロパティを比較的簡単に設定することもできます。これはclone()を実行するのと同じではありませんが、多くの実用的な目的のためには問題ありません。
clone()
のすべての癖を100%認識していない場合は、それを避けてください。 clone()
が壊れているとは言いません。私は言うだろう:あなたがそれがあなたの最良の選択肢であることを完全に確信しているときだけ、それを使う。コピーコンストラクター(またはファクトリーメソッド、それは本当に重要ではない)は書くのが簡単で(おそらく長くても簡単です)、コピーしたいものだけをコピーし、コピーしたい方法をコピーします。必要に応じてトリミングできます。
さらに、コピーコンストラクター/ファクトリーメソッドを呼び出したときに何が起こるかを簡単にデバッグできます。
そして、clone()
は、参照(たとえばCollection
への)だけがコピーされることを意味すると仮定して、オブジェクトの「深い」コピーをすぐに作成しません。しかし、ここでディープとシャローの詳細をお読みください: ディープコピー、シャローコピー、クローン
Cloneableインターフェイスは役に立たないという意味で壊れていますが、クローンはうまく機能し、大きなオブジェクト(8フィールド以上)のパフォーマンスが向上する可能性がありますが、エスケープ分析は失敗します。ほとんどの場合、コピーコンストラクターを使用することをお勧めします。配列でのクローンの使用は、長さが同じであることが保証されているため、Arrays.copyOfよりも高速です。
詳細はこちら https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
通常、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回。うまくいけば、それがリソースをロックしている場合、それを知っているでしょう。