オブジェクトの深いコピーをどのように作成しますか?


301

オブジェクトの深いコピー機能を実装するのは少し難しいです。元のオブジェクトと複製されたオブジェクトが参照を共有しないようにするためにどのような手順を実行しますか?


4
Kryoには、コピー/クローン作成のサポートが組み込まれています。これはオブジェクトからオブジェクトへの直接コピーであり、object-> bytes-> objectではありません。
NateS

1
後で尋ねられた関連する質問は次のとおりです。ディープクローンユーティリティの
推奨

クローンライブラリを使用することで、私はその日を節約できました! github.com/kostaskougios/cloning
gaurav

回答:


168

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

これを効率的に行う方法についての記事を次に示します。

警告:クラスがシリアル化をオーバーライドして、新しいインスタンスが作成されないようにすることが可能です(シングルトンなど)。もちろん、クラスがSerializableでない場合、これは機能しません。


6
この記事で提供されているFastByteArrayOutputStream実装の方が効率的かもしれないことに注意してください。バッファーがいっぱいになると、ArrayListスタイルの拡張を使用しますが、LinkedListスタイルの拡張アプローチを使用することをお勧めします。新しい2xバッファーを作成して現在のバッファーを上書きする代わりに、リンクされたバッファーのリストを維持し、現在のバッファーがいっぱいになったときに新しいバッファーを追加します。デフォルトのバッファーサイズに収まらないデータを書き込むリクエストを受け取った場合は、リクエストとまったく同じサイズのバッファーノードを作成します。ノードは同じサイズである必要はありません。
ブライアンハリス


連載を通じて深いコピーを説明する良い記事、:javaworld.com/article/2077578/learn-java/...
無限に

@BrianHarrisリンクリストは、動的配列ほど効率的ではありません。動的配列への要素の挿入は定数の複雑さを償却し、リンクリストへの挿入は線形の複雑さ
Norill Tempest

コピーコンストラクターアプローチよりもどれだけシリアライズとデシリアライズが遅くなりますか?
Woland

75

数人がの使用または上書きについて言及していますObject.clone()。しないでください。Object.clone()いくつかの大きな問題があり、ほとんどの場合、その使用は推奨されません。完全な回答については、Joshua Blochの「Effective Java」のアイテム11を参照してください。Object.clone()プリミティブ型の配列で安全に使用できると思いますが、それとは別に、クローンの適切な使用とオーバーライドについて慎重に検討する必要があります。

シリアル化(XMLまたはその他)に依存するスキームは、扱いにくいです。

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


58

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

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

クラスをバイトのストリームに変換します。

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
クラスがfinalの場合、どのように拡張しますか?
Kumar Manish

1
@KumarManishクラスMyContainerはSerializable {MyFinalClassインスタンスを実装します。...}
Matteo T.

これは素晴らしい返事だと思います。クローンはめちゃくちゃです
blackbird014 '19

@MatteoT。instanceこの場合、非シリアル化可能なクラスプロパティはどのようにシリアル化され、非シリアル化できますか?
ファリッド

40

org.apache.commons.lang3.SerializationUtils.clone(T)Apache Commons Lang を使用して、シリアライゼーションベースのディープクローンを作成できますが、パフォーマンスはひどいです。

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


それはまた利用可能ですorg.apache.commons.lang.SerializationUtils
ピノ

25

ディープコピーを実装する1つの方法は、関連する各クラスにコピーコンストラクターを追加することです。コピーコンストラクターは、 'this'のインスタンスを1つの引数として取り、そこからすべての値をコピーします。かなりの作業ですが、かなり簡単で安全です。

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

例:

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
コピーしているものがサブクラスであるが、親によって参照されている場合にのみ興味があります。コピーコンストラクタをオーバーライドすることは可能ですか?
豚肉'N'バニー

親クラスがそのサブクラスを参照するのはなぜですか?例を挙げていただけますか?
Adriaan Koster 2013

1
パブリッククラスCarはVehicleを拡張し、車両を車両と呼びます。originaList = new ArrayList <Vehicle>; copyList = new ArrayList <Vehicle>; originalList.add(new Car()); for(Vehicle vehicle:vehicleList){copyList.add(new Vehicle(vehicle)); }
Pork 'n' Bunny

@AdriaanKoster:元のリストにが含まれているToyota場合、コードはCar宛先リストにを入れます。適切なクローン作成では、通常、クラスが仮想ファクトリメソッドを提供する必要があります。その契約では、独自のクラスの新しいオブジェクトを返すと規定されています。コピーコンストラクタ自体はprotected、正確なタイプがコピーされるオブジェクトのタイプと一致するオブジェクトを構築するためにのみ使用されることを保証する必要があります)。
スーパーキャット2013

だから私があなたの提案を正しく理解すると、ファクトリメソッドはプライベートコピーコンストラクタを呼び出すでしょうか?サブクラスのコピーコンストラクターは、スーパークラスフィールドが確実に初期化されるようにする方法を教えてください。例を挙げていただけますか?
Adriaan Koster 2013

20

シンプルなAPIを備え、リフレクションを使用して比較的高速なクローン作成を実行するライブラリ使用できます(シリアル化メソッドよりも高速でなければなりません)。

Cloner cloner = new Cloner();

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

19

Apache commonsはオブジェクトをディープクローンする高速な方法を提供します。

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

1
これは、Serializableを実装するオブジェクトと、Serializableを実装するすべてのフィールドでのみ機能します。
2018年

11

XStreamは、このような場合に非常に役立ちます。これはクローンを作成するための簡単なコードです

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

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

1
いいえ、オブジェクトのxml-ingのオーバーヘッドは必要ありません。
egelev 2015

@egeleve '08からのコメントに返信していることをご存知ですか?私はもはやJavaを使用せず、おそらく今より良いツールがあるでしょう。ただし、そのときは、別の形式にシリアル化してからシリアル化するのは良いハックのようでした。これは明らかに非効率的でした。
sankara


10

以下のための春のフレームワークのユーザー。クラスを使用org.springframework.util.SerializationUtils

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}

9

複雑なオブジェクトの場合、パフォーマンスがそれほど重要でない場合は、gsonなどのjsonライブラリを使用し てオブジェクトをjsonテキストにシリアル化し、テキストを逆シリアル化して新しいオブジェクトを取得します。

リフレクションに基づくgsonは、ほとんどの場合に機能しtransientますが、フィールドがコピーされず、原因で循環参照を持つオブジェクトがコピーされますStackOverflowError

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);
}

3
あなた自身と私たちのために、Javaの命名規則に従ってください。
Patrick Bergner、2017年

8

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


7

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


2
「適切な設計手法」について教えてください。
fklappan 2016年

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));
    }
}

5

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



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クラスはシリアライズ可能なインターフェースを実装する必要があります


2

Jacksonを使用してオブジェクトをシリアル化および逆シリアル化します。この実装では、オブジェクトがSerializableクラスを実装する必要はありません。

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

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