2008-09-15 13 views
226

Javaでは、ディープオブジェクトコピー機能を実装するのが少し難しいです。元のオブジェクトと複製されたオブジェクトが確実に参照を共有しないようにするために必要な手順は?Javaでオブジェクトのディープコピーを作成するにはどうすればよいですか?

+4

をKryoが組み込まれている[コピー/クローニング]のサポート(https://code.google.com/p/kryo/#Copying/cloning)。これは、オブジェクト - >バイト - >オブジェクトではなく、オブジェクトからオブジェクトへの直接コピーです。 – NateS

+1

これは、後で尋ねられる関連する質問です。[Deep clone utility recomendation](0120-18756-1) –

答えて

135

安全な方法は、オブジェクトをシリアル化して逆シリアル化することです。これは、すべてがまったく新しいリファレンスであることを保証します。

Here's an articleこれを効率的に行う方法については、

警告:新しいインスタンスがではなくでないように、クラスがシリアル化をオーバーライドすることは可能です。シングルトンの場合あなたのクラスがシリアライズ可能でない場合、これはもちろん動作しません。

+5

記事で提供されているFastByteArrayOutputStream実装がより効率的になる可能性があることに注意してください。バッファがいっぱいになるとArrayListスタイルの展開が使用されますが、LinkedListスタイルの展開方法を使用する方が効果的です。新しい2xバッファを作成し、現在のバッファをmemcpyする代わりに、バッファのリンクリストを維持し、現在のバッファがいっぱいになると新しいバッファを追加します。デフォルトのバッファサイズよりも多くのデータを書き込む要求がある場合は、要求とまったく同じ大きさのバッファノードを作成してください。ノードは同じサイズである必要はありません。 –

+0

ちょうどkryoを使用してください:https://github.com/EsotericSoftware/kryo#copyingcloningベンチマークhttp://www.slideshare.net/AlexTumanoff/serialization-and-performance – zengr

+0

シリアル化による深いコピーを説明している良い記事:http ://www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html –

6

ディープコピーは、各クラスの同意がある場合にのみ行うことができます。クラス階層を制御できる場合は、クローン可能なインタフェースを実装して、Cloneメソッドを実装できます。それ以外の場合、オブジェクトが非データリソース(データベース接続など)を共有している可能性があるため、完全コピーを実行することは安全には不可能です。一般的に、ディープ・コピーはJava環境では悪い習慣とみなされ、適切な設計方法で避けるべきです。

+1

「適切な設計方法」について教えてください。 – eftokay83

32

Commons Langでorg.apache.commons.lang3.SerializationUtils.clone(T)を使用してシリアル化ベースのディープクローンを実行できますが、パフォーマンスは非常に悪いです。

一般に、クローン作成が必要なオブジェクトグラフには、オブジェクトの各クラスごとに独自のクローンメソッドを記述することをお勧めします。

8

XStream(http://x-stream.github.io/)を使用してください。アノテーションで無視できるプロパティや、XStreamクラスのプロパティ名を明示的に指定することもできます。さらに、クローン可能なインタフェースを実装する必要はありません。

11

このような場合、XStreamは本当に便利です。ここではいくつかの人々が使用してまたは上書きObject.clone()言及している

private static final XStream XSTREAM = new XStream(); 
... 

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj)); 
+0

いいえ、あなたはオブジェクトをxml-ingするオーバーヘッドを必要としません。 – egelev

+0

@egeleveあなたは08年のコメントから返信していることを認識していますか?私はもうJavaを使用していないし、おそらく今より良いツールがあるだろう。しかし、その時点では、別の形式にシリアル化してからシリアル化することは良いハックのように思えました。間違いなく非効率的でした。 – sankara

57

クローニング行う簡単なコードです。それをしないでください。 Object.clone()にはいくつかの重大な問題があり、ほとんどの場合、その使用は推奨されません。ジョシュア・ブロッホの「Effective Java」の項目11を参照してください。私は安全にObject.clone()をプリミティブ型の配列に使うことができると信じていますが、それ以外には、適切にクローンを使用してオーバーライドすることを賢明にする必要があります。

シリアライゼーション(XMLなど)に依存しているスキームは、クルージングです。

ここで簡単な答えはありません。オブジェクトをディープコピーする場合は、オブジェクトグラフをトラバースし、各子オブジェクトをオブジェクトのコピーコンストラクタまたは静的ファクトリメソッドを介して明示的にコピーする必要があります。 Immutables(例:String)はコピーする必要はありません。脇に、あなたはこの理由のために不変性を好むべきです。

6
import com.thoughtworks.xstream.XStream; 

public class deepCopy { 
    private static XStream xstream = new XStream(); 

    //serialize with Xstream them deserialize ... 
    public static Object deepCopy(Object obj){ 
     return xstream.fromXML(xstream.toXML(obj)); 
    } 
} 
44

ファイルを作成せずにシリアライズでディ​​ープコピーを作成できます。

ディープコピーするオブジェクトにはimplement serializableが必要です。クラスがfinalでないか、または変更できない場合は、クラスを拡張してシリアライズ可能に実装します。

ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
ObjectOutputStream oos = new ObjectOutputStream(bos); 
oos.writeObject(object); 
oos.flush(); 
oos.close(); 
bos.close(); 
byte[] byteData = bos.toByteArray(); 

はバイトストリームからクラスを復元​​します:バイトのストリームにあなたのクラスに変換

深いコピーを実装するために

ByteArrayInputStream bais = new ByteArrayInputStream(byteData); 
(Object) object = (Object) new ObjectInputStream(bais).readObject(); 
+4

クラスが最終的な場合はどのように拡張しますか? –

+1

@KumarManishクラスMyContainerは、Serializable {MyFinalClassインスタンスを実装しています。 ...} –

+0

これはすばらしい返答です。クローンが混乱している – blackbird014

22

一つの方法は、それぞれ関連するにコピーコンストラクタを追加することですクラス。コピーコンストラクタは 'this'のインスタンスを単一の引数として引数からすべての値をコピーします。かなりの仕事ですが、かなり簡単で安全です。

編集:フィールドを読み取るためにアクセサメソッドを使用する必要はありません。ソースインスタンスは常にコピーコンストラクタのインスタンスと同じ型なので、すべてのフィールドに直接アクセスできます。明らかだが、見落とされるかもしれない。

例:

public class Order { 

    private long number; 

    public Order() { 
    } 

    /** 
    * Copy constructor 
    */ 
    public Order(Order source) { 
     number = source.number; 
    } 
} 


public class Customer { 

    private String name; 
    private List<Order> orders = new ArrayList<Order>(); 

    public Customer() { 
    } 

    /** 
    * Copy constructor 
    */ 
    public Customer(Customer source) { 
     name = source.name; 
     for (Order sourceOrder : source.orders) { 
      orders.add(new Order(sourceOrder)); 
     } 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 
} 

編集:コピーコンストラクタを使用しているとき、あなたがコピーしているオブジェクトの実行時の型を知っておく必要があることに注意してください。上記の方法では、混合リストを簡単にコピーすることはできません(リフレクションコードで行うこともできます)。

+1

コピーしているものがサブクラスであるが、親によって参照されているというケースに興味があります。コピーコンストラクタをオーバーライドすることは可能ですか? –

+0

親クラスがそのサブクラスを参照するのはなぜですか?あなたは例を挙げることができますか? –

+1

public class Car extends Vehicle そして車を乗り物として参照してください。 originaList = new ArrayList ; copyList = new ArrayList ; originalList.add(new Car()); (車両:車両リスト){ copyList.add(新車(車両)); } –

9

非常に簡単で簡単なアプローチの1つは、Jackson JSONを使用して複雑なJavaオブジェクトをJSONにシリアル化して読み戻すことです。

http://wiki.fasterxml.com/JacksonInFiveMinutes

+0

リンクが無効です。 –

12

あなたは、単純なAPIを持ち、かつ反射と比較的高速クローニングを行いuse a libraryは、(直列化メソッドよりも高速でなければなりません)することができます。

Cloner cloner = new Cloner(); 

MyClass clone = cloner.deepClone(o); 
// clone is a deep-clone of o 
4

私は、JavaオブジェクトをクローニングするためDozerを使用し、それがKryoライブラリは別の偉大な選択肢である、それで素晴らしいことです。

14

Apacheコモンズは、オブジェクトを深くクローンするための速い方法を提供します。

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1); 
+0

テスト済みで安定したライブラリを提案するためのupvote – katzenhut

2

BeanUtils本当に良い仕事の深いクローン豆です。

BeanUtils.cloneBean(obj); 
+2

浅いクローニングを行います。 –

2

1)

ここ
public static Object deepClone(Object object) { 
    try { 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(baos); 
    oos.writeObject(object); 
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 
    ObjectInputStream ois = new ObjectInputStream(bais); 
    return ois.readObject(); 
    } 
    catch (Exception e) { 
    e.printStackTrace(); 
    return null; 
    } 
} 

2) 

    // (1) create a MyPerson object named Al 
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India"); 
    MyPerson al = new MyPerson("Al", "Arun", address); 

    // (2) make a deep clone of Al 
    MyPerson neighbor = (MyPerson)deepClone(al); 

あなたMyPersonとこのMyAddressクラスは複雑なオブジェクトの場合serilazableインタフェース

3

を実装する必要がありますし、パフォーマンスが重要ではないときに私はシリアル化するためにgson のように、JSONライブラリを使用オブジェクトをjsonのテキストに変換し、次に新しいオブジェクトを取得するためにテキストを逆シリアル化します。

transientフィールドはコピーされず、循環参照を持つオブジェクトはStackOverflowErrorであることを除いて、ほとんどの場合、反射に基づくgsonが機能します。 春のフレームワークユーザーの場合

public static <T> T Copy(T AnObject, Class<T> ClassInfo) 
{ 
    Gson gson = new GsonBuilder().create(); 
    String text = gson.toJson(AnObject); 
    T newObject = gson.fromJson(text, ClassInfo); 
    return newObject; 
} 
public static void main(String[] args) 
{ 
    String originalObject = "hello"; 
    String copiedObject = Copy(originalObject, String.class); 

} 
+1

あなた自身と私たちのためにJavaの命名規則に従ってください。 –

4

。クラスorg.springframework.util.SerializationUtils使用:

@SuppressWarnings("unchecked") 
public static <T extends Serializable> T clone(T object) { 
    return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object)); 
} 
関連する問題