ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java对象的深拷贝

2021-06-15 04:01:40  阅读:159  来源: 互联网

标签:Java 对象 address getAddress User Address new 拷贝 pm


综述

当我们想要在 Java 中复制一个对象时,我们需要考虑两种可能性,浅拷贝和深拷贝。

对于浅拷贝方法,我们只拷贝字段值,因此拷贝可能依赖于原始对象。在深度复制方法中,我们确保树中的所有对象都被深度复制,因此副本不依赖于任何可能会更改的先前存在的对象。

Maven设置

我们将使用三个Maven依赖项Gson、Jackson和Apache Commons Lang来测试深拷贝的不同方式。

<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>

Model

为了比较复制 Java 对象的不同方法,我们需要两个类

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
}

浅拷贝

浅拷贝是一种只将字段的值从一个对象复制到另一个对象。A shallow copy is one in which we only copy values of fields from one object to another:

@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,这意味着它们是不同的对象;然而,当我们改变任何原始Address的属性时,这也会影响到shallowCopy的Address对象。

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

深拷贝

深拷贝是解决此问题的替代方案。它的优点是对象图中的每个可变对象都是递归复制的(each mutable object in the object graph is recursively copied.)。

因为复制不依赖于之前创建的任何可变对象,所以它不会像我们在浅复制中看到的那样被意外修改。

复制构造函数(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()));
}

在上面的深拷贝实现中,我们没有在复制构造函数中创建新的String,因为String是一个不可变类,因此,它们不能被意外修改。

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

Cloneable接口

该实现基于从Object继承的clone方法。它是受保护(protected)的,但我们需要对它进行重写为public的。

我们还需要向类添加一个标记接口 Cloneable,以表明这些类实际上是cloneable的。

将clone()方法添加到Address类中:

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

为User类实现clone()方法:

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

注意,super.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());
}

External Libraries

上面的例子看起来很简单,但有时当我们不能添加额外的构造函数或重写克隆方法时,它们就不能作为解决方案。

比如当我们没有源代码时,或者当object graph非常复杂,如果我们专注于编写额外的构造函数或在对象图中的所有类上实现clone()方法。

为了实现深度复制,我们可以序列化一个对象,然后将其反序列化为一个新对象。

Apache Commons Lang

Apache Commons Lang 有 SerializationUtils#clone,当对象图中的所有类都实现 Serializable 接口时,它会执行深度复制。

如果该方法遇到不可序列化的类,它将失败并抛出未经检查的 SerializationException,因此,我们需要将Serializable接口添加到类中。

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

Gson的JSON序列化

与Apache Commons Lang不同,GSON不需要Serializable接口来进行转换。

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

Jackson的JSON序列化

此实现与使用Gson的实现非常相似,但我们需要将默认构造函数添加到类中。

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

标签:Java,对象,address,getAddress,User,Address,new,拷贝,pm
来源: https://www.cnblogs.com/CodePastry/p/14883958.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有