• Java对象的深拷贝


    综述

    当我们想要在 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());
    }
  • 相关阅读:
    数学考试
    奇♂妙拆分
    11.25
    11.21
    11.20
    11.19
    11.18
    11.15
    11.14作业
    11.14
  • 原文地址:https://www.cnblogs.com/CodePastry/p/14883958.html
Copyright © 2020-2023  润新知