オブジェクトの深いコピー機能を実装するのは少し難しいです。元のオブジェクトと複製されたオブジェクトが参照を共有しないようにするために、どのような手順を実行しますか?
20 に答える
安全な方法は、オブジェクトをシリアル化してから逆シリアル化することです。これにより、すべてが真新しいリファレンスになります。
これを効率的に行う方法については、次の記事を参照してください。
警告:シングルトンなどの新しいインスタンスが作成されないように、クラスがシリアライゼーションをオーバーライドする可能性があります。また、クラスがシリアライズ可能でない場合、これはもちろん機能しません。
一部の人々は、使用またはオーバーライドについて言及していますObject.clone()
。やらないでください。Object.clone()
にはいくつかの重大な問題があり、ほとんどの場合、その使用はお勧めできません。完全な回答については、Joshua Bloch による「 Effective Java 」の項目 11 を参照してください。プリミティブ型の配列で安全に使用できると思いますがObject.clone()
、それとは別に、クローンを適切に使用してオーバーライドすることについて慎重に考える必要があります。
シリアライゼーション (XML など) に依存するスキームは、扱いにくいものです。
ここには簡単な答えはありません。オブジェクトをディープ コピーする場合は、オブジェクト グラフを走査し、オブジェクトのコピー コンストラクターまたは子オブジェクトをディープ コピーする静的ファクトリ メソッドを介して、各子オブジェクトを明示的にコピーする必要があります。不変 (例: String
s) はコピーする必要はありません。余談ですが、この理由から、不変性を優先する必要があります。
ファイルを作成せずにシリアル化でディープ コピーを作成できます。
ディープ コピーするオブジェクトは、implement serializable
. クラスが final でないか、変更できない場合は、クラスを拡張して 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 = new ObjectInputStream(bais).readObject();
Apache Commons Langを使用してシリアライゼーション ベースのディープ クローンを実行できますが、org.apache.commons.lang3.SerializationUtils.clone(T)
注意してください。パフォーマンスは最悪です。
一般に、複製が必要なオブジェクト グラフ内のオブジェクトのクラスごとに、独自の複製メソッドを作成することをお勧めします。
ディープ コピーを実装する 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;
}
}
編集: コピー コンストラクターは継承を考慮しないことに注意してください。例: OnlineOrder (Order のサブクラス) をコピー コンストラクターに渡すと、明示的に解決しない限り、通常の Order インスタンスがコピー内に作成されます。リフレクションを使用して、引数の実行時型でコピー コンストラクターを検索できます。ただし、継承を一般的な方法でカバーする必要がある場合は、このルートに行かずに別の解決策を探すことをお勧めします。
シンプルな API を持つライブラリを使用でき、リフレクションを使用して比較的高速にクローン作成を実行できます (シリアル化メソッドよりも高速である必要があります)。
Cloner cloner = new Cloner();
MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
複雑なオブジェクトの場合、パフォーマンスが重要でない場合は、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);
}
XStream は、そのような場合に非常に役立ちます。これはクローン作成を行うための簡単なコードです
private static final XStream XSTREAM = new XStream();
...
Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
非常に簡単でシンプルなアプローチの1つは、Jackson JSONを使用して複雑なJavaオブジェクトをJSONにシリアル化し、それを読み戻すことです。
https://github.com/FasterXML/jackson-databind/#5-minute-tutorial-streaming-parser-generatorから:
JsonFactory f = mapper.getFactory(); // may alternatively construct directly too
// First: write simple JSON output
File jsonFile = new File("test.json");
JsonGenerator g = f.createGenerator(jsonFile);
// write JSON: { "message" : "Hello world!" }
g.writeStartObject();
g.writeStringField("message", "Hello world!");
g.writeEndObject();
g.close();
// Second: read file back
JsonParser p = f.createParser(jsonFile);
JsonToken t = p.nextToken(); // Should be JsonToken.START_OBJECT
t = p.nextToken(); // JsonToken.FIELD_NAME
if ((t != JsonToken.FIELD_NAME) || !"message".equals(p.getCurrentName())) {
// handle error
}
t = p.nextToken();
if (t != JsonToken.VALUE_STRING) {
// similarly
}
String msg = p.getText();
System.out.printf("My message to you is: %s!\n", msg);
p.close();
XStream( http://x-stream.github.io/ ) を使用します。注釈を使用するか、プロパティ名を XStream クラスに明示的に指定することで、どのプロパティを無視できるかを制御することもできます。さらに、クローン可能なインターフェースを実装する必要はありません。
ディープ コピーは、各クラスの同意がある場合にのみ実行できます。クラス階層を制御できる場合は、複製可能なインターフェイスを実装し、Clone メソッドを実装できます。そうしないと、オブジェクトがデータ以外のリソース (データベース接続など) を共有している可能性があるため、ディープ コピーを安全に実行することは不可能です。ただし、一般に、ディープ コピーは Java 環境では不適切な方法と見なされており、適切な設計方法に従って回避する必要があります。
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));
}
}
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 クラスはシリアライズ可能なインターフェースを実装する必要があります
以下は、バイト配列ストリームを使用したオブジェクトのシリアル化と逆シリアル化を使用した一般的なディープ クローニング メソッドです (ファイルへの書き込みを回避するため)。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepClone(T t) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);) {
oos.writeObject(t);
byte[] bytes = baos.toByteArray();
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}