web-dev-qa-db-ja.com

Javaでオブジェクトのディープコピーをどこのように作成しますか?

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

273
Andrei Savu

安全な方法は、オブジェクトをシリアライズしてからデシリアライズすることです。これはすべてが真新しい参照であることを保証します。

これが記事です これを効率的に行う方法について。

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

160
Jason Cohen

少数の人々がObject.clone()の使用またはオーバーライドについて述べています。しないでください。 Object.clone()にはいくつかの大きな問題があり、その使用はほとんどの場合推奨されません。完全な答えについては、Joshua Blochによる " Effective Java "のItem 11を見てください。私はあなたが安全にObject.clone()をプリミティブ型配列に使うことができると思いますが、それとは別にあなたはクローンを適切に使いそして上書きすることについて慎重になる必要があります。

シリアライゼーション(XMLまたはその他)に依存している方式は非常に簡単です。

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

67
Julien Chastang

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

ディープコピーしたいオブジェクトは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();
53
Thargor

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

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

36
user8690

ディープコピーを実装する1つの方法は、関連する各クラスにコピーコンストラクタを追加することです。コピーコンストラクタは '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;
    }
}

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

23
Adriaan Koster

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

My_Object object2= org.Apache.commons.lang.SerializationUtils.clone(object1);
18
TheByeByeMan

あなたは シンプルなAPIを持ち、リフレクションを使って比較的速いクローン作成を実行するライブラリ を使うことができます(直列化メソッドより速いはずです)。

Cloner cloner = new Cloner();

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

XStreamはそのような場合に本当に便利です。これはクローニングを行うための簡単なコードです。

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

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

非常に簡単で簡単な方法の1つは、Jackson JSONを使用して複雑なJavaオブジェクトをJSONにシリアライズして読み戻すことです。

http://wiki.fasterxml.com/JacksonInFiveMinutes

9
Ravi Chinoy

Spring Frameworkユーザー向け。クラスorg.springframework.util.SerializationUtilsを使う:

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

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

8
Adi

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

7
Orion Adrian

複雑なオブジェクトの場合やパフォーマンスが重要でない場合は、 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);
}
6
tiboo
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));
    }
}
6
user946968

私は Dozer をJavaオブジェクトのクローン作成に使用しましたが、それは素晴らしいことです--- Kryo ライブラリはもう1つの優れた選択肢です。

4
supernova

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
Arun

BeanUtils ビーンのディープクローニングには本当にいい仕事をします。

BeanUtils.cloneBean(obj);
1
Alfergon