導入
Java言語では、オブジェクトをコピーする必要がある場合、コピーにはシャローコピーとディープコピーの2種類があります。浅いコピーはソースオブジェクトのアドレスのみをコピーするため、ソースオブジェクトの値が変更されると、コピーされたオブジェクトの値も変更されます。ディープコピーはソースオブジェクトのすべての値をコピーするため、ソースオブジェクトの値が変更されても、コピーされたオブジェクトの値は変更されません。
浅いコピーと深いコピーの違いを理解した後、このブログでは、深いコピーのいくつかの方法を説明します。
オブジェクトをコピーする
まず、コピーする必要のある単純なオブジェクトを定義しましょう。
/**
* 用户
*/
public class User {
private String name;
private Address address;
// constructors, getters and setters
}
/**
* 地址
*/
public class Address {
private String city;
private String country;
// constructors, getters and setters
}
上記のコードに示されているように、名前とアドレスを含むUserユーザークラスを定義します。ここで、addressは文字列ではなく、国と都市を含む別のAddressクラスです。コンストラクターとメンバー変数のget()メソッドとset()メソッドは、ここでは省略されています。次に、Userオブジェクトをディープコピーする方法について詳しく説明します。
メソッドコンストラクタ
コンストラクターを呼び出すことでディープコピーを作成できます。仮パラメーターが基本型と文字列の場合は直接割り当てられ、オブジェクトの場合は新しいパラメーターが作成されます。
テストケース
@Test
public void constructorCopy() {
Address address = new Address("杭州", "中国");
User user = new User("大山", address);
// 调用构造函数时进行深拷贝
User copyUser = new User(user.getName(), new Address(address.getCity(), address.getCountry()));
// 修改源对象的值
user.getAddress().setCity("深圳");
// 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
メソッド2はclone()メソッドをオーバーロードします
Object親クラスにはclone()コピーメソッドがありますが、これは保護されたタイプであるため、オーバーライドしてパブリックタイプに変更する必要があります。さらに、サブクラスは、クラスをコピーできることをJVMに通知するために、Cloneableインターフェースを実装する必要もあります。
コードを書き直します
UserクラスとAddressクラスを変更して、ディープコピーをサポートするCloneableインターフェイスを実装しましょう。
/**
* 地址
*/
public class Address implements Cloneable {
private String city;
private String country;
// constructors, getters and setters
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
/**
* 用户
*/
public class User implements Cloneable {
private String name;
private Address address;
// constructors, getters and setters
@Override
public User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.setAddress(this.address.clone());
return user;
}
}
super.clone()は実際には浅いコピーであるため、Userクラスのclone()メソッドを書き換える場合は、address.clone()を呼び出してアドレスオブジェクトを再割り当てする必要があることに注意してください。
テストケース
@Test
public void cloneCopy() throws CloneNotSupportedException {
Address address = new Address("杭州", "中国");
User user = new User("大山", address);
// 调用clone()方法进行深拷贝
User copyUser = user.clone();
// 修改源对象的值
user.getAddress().setCity("深圳");
// 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法3ApacheCommonsLangシリアル化
Javaはシリアル化する機能を提供します。最初にソースオブジェクトをシリアル化し、次に逆シリアル化してコピーオブジェクトを生成できます。ただし、シリアル化を使用する前提は、コピーされたクラス(そのメンバー変数を含む)がSerializableインターフェイスを実装する必要があることです。Apache Commons LangパッケージはJavaシリアル化をカプセル化し、直接使用できます。
コードを書き直します
UserクラスとAddressクラスを変更して、シリアル化をサポートするSerializableインターフェイスを実装しましょう。
/**
* 地址
*/
public class Address implements Serializable {
private String city;
private String country;
// constructors, getters and setters
}
/**
* 用户
*/
public class User implements Serializable {
private String name;
private Address address;
// constructors, getters and setters
}
テストケース
@Test
public void serializableCopy() {
Address address = new Address("杭州", "中国");
User user = new User("大山", address);
// 使用Apache Commons Lang序列化进行深拷贝
User copyUser = (User) SerializationUtils.clone(user);
// 修改源对象的值
user.getAddress().setCity("深圳");
// 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法4Gsonシリアル化
GsonはオブジェクトをJSONにシリアル化でき、JSONをオブジェクトに逆シリアル化できるため、ディープコピーに使用できます。
テストケース
@Test
public void gsonCopy() {
Address address = new Address("杭州", "中国");
User user = new User("大山", address);
// 使用Gson序列化进行深拷贝
Gson gson = new Gson();
User copyUser = gson.fromJson(gson.toJson(user), User.class);
// 修改源对象的值
user.getAddress().setCity("深圳");
// 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
方法5ジャクソンのシリアル化
Jacksonは、オブジェクトをJSONにシリアル化できるという点で、Gsonに似ています。明らかな違いは、コピーされたクラス(そのメンバー変数を含む)にデフォルトの引数なしコンストラクターが必要なことです。
コードを書き直します
Userクラス、Addressクラスを変更し、Jacksonをサポートするためにデフォルトの引数なしコンストラクターを実装しましょう。
/**
* 用户
*/
public class User {
private String name;
private Address address;
// constructors, getters and setters
public User() {
}
}
/**
* 地址
*/
public class Address {
private String city;
private String country;
// constructors, getters and setters
public Address() {
}
}
テストケース
@Test
public void jacksonCopy() throws IOException {
Address address = new Address("杭州", "中国");
User user = new User("大山", address);
// 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
User copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), User.class);
// 修改源对象的值
user.getAddress().setCity("深圳");
// 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());
}
要約する
非常に多くのディープコピーの実装方法を述べましたが、どの方法が最適ですか?最も簡単な判断は、コピーされたクラス(そのメンバー変数を含む)がディープコピーコンストラクターを提供するかどうか、Cloneableインターフェイスを実装するかどうか、Serializableインターフェイスを実装するかどうか、デフォルトの引数なしコンストラクターを実装するかどうかによって選択することです。詳細な検討事項については、以下の表を参照してください。