如何对JAVA对象进行深拷贝

引自:How to Make a Deep Copy of an Object in Java

1. 介绍
当我们想克隆一个对象时,有两种解决方案--- 浅拷贝(shadow copy)或深拷贝(deep clone)。
浅拷贝只拷贝对象的成员变量,因此和原对象有依赖关系;而使用深拷贝时,必须确保所有的对象树(object tree)都被复制,使得拷贝体不依赖与原来存在的对象。
在此文中,我们将比较者两种拷贝方式,学习4种深拷贝的方法。
复制代码
2.Maven依赖
我们将使用Gson、JackJon和Apache Commons Lang的依赖,测试深拷贝不同实现方式的表现。
以下是maven依赖的pom.xml
复制代码
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.3</version>
</dependency>
复制代码
3. Model

先定义一下Model

class Address {
    private String street;
    private String city;
    private String country;
    // standard constructors, getters and setters
}
复制代码
class User {
    private String firstName;
    private String lastName;
    private Address address;
    // standard constructors, getters and setters
}
复制代码
浅拷贝 Shadow Copy

以下演示浅拷贝只复制成员变量的值

@Test
public void whenShallowCopying_thenObjectsShouldNotBeSame() {
 
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
     
    User shallowCopy = new User(
      pm.getFirstName(), pm.getLastName(), pm.getAddress());
 
    assertThat(shallowCopy)
      .isNotSameAs(pm);
}
复制代码

实例中得到 pm != shallowCopy,意味着他们虽然他两是不同的对象,但是当一个对象改变了任何原有的属性值是,也会影响另一个对象。

@Test
public void whenModifyingOriginalObject_ThenCopyShouldChange() {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    User shallowCopy = new User(
      pm.getFirstName(), pm.getLastName(), pm.getAddress());
 
    address.setCountry("Great Britain");
    assertThat(shallowCopy.getAddress().getCountry())
      .isEqualTo(pm.getAddress().getCountry());
}
复制代码
5. 深拷贝 deep copy

深拷贝就是为了解决浅拷贝的问题。它的优势在于至少每个可变对象是递归拷贝的。 既然深拷贝不依赖于任何可变对象,它将不会被原始拷贝所影响。 下面我们将看到几种深拷贝的实现方式和他们的优势

5.1 Copy Constructor
public Address(Address that) {
    this(that.getStreet(), that.getCity(), that.getCountry());
}
复制代码
public User(User that) {
    this(that.getFirstName(), that.getLastName(), new Address(that.getAddress()));
}
复制代码

In the above implementation of the deep copy, we haven’t created new Strings in our copy constructor because String is an immutable class.

As a result, they can’t be modified by accident. Let’s see if this works:

@Test
public void whenModifyingOriginalObject_thenCopyShouldNotChange() {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    User deepCopy = new User(pm);
 
    address.setCountry("Great Britain");
    assertNotEquals(
      pm.getAddress().getCountry(), 
      deepCopy.getAddress().getCountry());
}
复制代码
5.2 Cloneable Interface

The second implementation is based on the clone method inherited from Object. It’s protected, but we need to override it as public.

We’ll also add a marker interface, Cloneable, to the classes to indicate that the classes are actually cloneable.

Let’s add the clone() method to the Address class:

@Override
public Object clone() {
    try {
        return (Address) super.clone();
    } catch (CloneNotSupportedException e) {
        return new Address(this.street, this.getCity(), this.getCountry());
    }
}
复制代码

And now let’s implement clone() for the User class:

@Override
public Object clone() {
    User user = null;
    try {
        user = (User) super.clone();
    } catch (CloneNotSupportedException e) {
        user = new User(
          this.getFirstName(), this.getLastName(), this.getAddress());
    }
    user.address = (Address) this.address.clone();
    return user;
}
复制代码

Note that the super.clone() call returns a shallow copy of an object, but we set deep copies of mutable fields manually, so the result is correct: super.clone() 返回一个对象的浅拷贝,但是我们可以手动的深拷贝可变成员变量,让object.clone成为深拷贝

@Test
public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange() {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    User deepCopy = (User) pm.clone();
 
    address.setCountry("Great Britain");
 
    assertThat(deepCopy.getAddress().getCountry())
      .isNotEqualTo(pm.getAddress().getCountry());
}
复制代码
6. External Libraries

The above examples look easy, but sometimes they don’t apply as a solution when we can’t add an additional constructor or override the clone method.

This might happen when we don’t own the code, or when the object graph is so complicated that we wouldn’t finish our project on time if we focused on writing additional constructors or implementing the clone method on all classes in the object graph.

What then? In this case, we can use an external library. To achieve a deep copy, we can serialize an object and then deserialize it to a new object.

Let’s look at a few examples.

6.1. Apache Commons Lang

Apache Commons Lang has SerializationUtils#clone, which performs a deep copy when all classes in the object graph implement the Serializable interface.

If the method encounters a class that isn’t serializable, it’ll fail and throw an unchecked SerializationException.

Because of that, we need to add the Serializable interface to our classes:

@Test
public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange() {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    User deepCopy = (User) SerializationUtils.clone(pm);
 
    address.setCountry("Great Britain");
 
    assertThat(deepCopy.getAddress().getCountry())
      .isNotEqualTo(pm.getAddress().getCountry());
}
复制代码
6.2 JSON Serialization with Gson

The other way to serialize is to use JSON serialization. Gson is a library that’s used for converting objects into JSON and vice versa.

Unlike Apache Commons Lang, GSON does not need the Serializable interface to make the conversions.

Let’s have a quick look at an example:

@Test
public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange() {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    Gson gson = new Gson();
    User deepCopy = gson.fromJson(gson.toJson(pm), User.class);
 
    address.setCountry("Great Britain");
 
    assertThat(deepCopy.getAddress().getCountry())
      .isNotEqualTo(pm.getAddress().getCountry());
}
复制代码
6.3. JSON Serialization with Jackson

Jackson is another library that supports JSON serialization. This implementation will be very similar to the one using Gson, but we need to add the default constructor to our classes.

Let’s see an example:

@Test
public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange() throws IOException {
    Address address = new Address("Downing St 10", "London", "England");
    User pm = new User("Prime", "Minister", address);
    ObjectMapper objectMapper = new ObjectMapper();
     
    User deepCopy = objectMapper
      .readValue(objectMapper.writeValueAsString(pm), User.class);
 
    address.setCountry("Great Britain");
 
    assertThat(deepCopy.getAddress().getCountry())
      .isNotEqualTo(pm.getAddress().getCountry());
}
复制代码
7. Conclusion

Which implementation should we use when making a deep copy? The final decision will often depend on the classes we’ll copy and whether we own the classes in the object graph.

As always, the complete code samples for this tutorial can be found over on Github

转载于:https://juejin.im/post/5c0e006151882569732423a4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值