Java中的深浅拷贝方式

x33g5p2x  于2022-06-27 转载在 Java  
字(5.1k)|赞(0)|评价(0)|浏览(272)

Java中的深浅拷贝方式

实现Cloneable重写clone()方法

如何进行对象克隆

Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:

① 实现Cloneable接口,这是一个标记接口,自身没有方法。

② 覆盖clone()方法,可见性提升为public。

浅拷贝和深拷贝

浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象

深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象

也就是说,一个默认的clone()方法实现机制,仍然是赋值,如果一个呗赋值的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝,如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法

浅拷贝案例
@Data
public class Person implements Cloneable {
    private String name;
    private Integer age;

    @Override
    public Person clone(){
        try {
            return (Person)super.clone();
        } catch (CloneNotSupportedException e) {
            return new Person();
        }
    }
}

测试结果

@Test
    public void test1() {
        Person p1 = new Person();
        p1.setAge(31);
        p1.setName("Peter");

        Person p2 = p1.clone();
        System.out.println(p1 == p2);//false
        p2.setName("Jacky");
        System.out.println("p1=" + p1);//p1=Person [name=Peter, age=31]
        System.out.println("p2=" + p2);//p2=Person(name=Jacky, age=31)

    }
深拷贝案例

修改person类,添加address属性

@Data
public class Address {
    private String type;
    private String value;
}

@Data
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Address address;

    @Override
    public Person clone(){
        try {
            return (Person)super.clone();
        } catch (CloneNotSupportedException e) {
            return new Person();
        }
    }
}

测试结果

@Test
    public void testShallowCopy(){
        Address address=new Address();
        address.setType("Home");
        address.setValue("北京");

        Person p1=new Person();
        p1.setAge(31);
        p1.setName("Peter");
        p1.setAddress(address);

        Person p2=p1.clone();
        System.out.println(p1==p2);//false

        p2.getAddress().setType("Office");
        System.out.println("p1="+p1);//p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
        System.out.println("p2="+p2);//p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
    }

这里我们可以发现只修改p2的address,可是结果显示p1和p2的address都进行了修改

这里如果要保证只修改p2的address,需要address实体类也实现Cloneable接口和重写clone方法,并且Person的clone()需要显式地clone其引用成员

@Data
public class Address implements Cloneable{
    private String type;
    private String value;

    @Override
    public Address clone(){
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            return new Address();
        }
    }
}

@Data
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Address address;

    @Override
    public Person clone(){
        try {
            Person person = (Person) super.clone();
            Address address = person.getAddress();
            person.setAddress(address.clone());
            return person;
        } catch (CloneNotSupportedException e) {
            return new Person();
        }
    }
}

重新跑前面的测试用例:

false
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

clone方式小结

  • 如果有一个非原生成员,如自定义对象的成员,那么就需要:

  • 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。

  • 同时,修改被复制类的clone()方法,增加成员的克隆逻辑

  • 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法

原型工厂类

为了方便测试,节省篇幅,封装一个工程类

public class PersonFactory{
    public static Person newPrototypeInstance(){
        Address address = new Address();
        address.setType("Home");
        address.setValue("北京");
 
        Person p1 = new Person();
        p1.setAddress(address);
        p1.setAge(31);
        p1.setName("Peter");
        return p1;
    }
}

利用Dozer拷贝对象

maven依赖

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>

测试用例:

@Test
    public void testDozer() {
        Person p1= PersonFactory.newPrototypeInstance();
        Mapper mapper = new DozerBeanMapper();
        Person p2 = mapper.map(p1, Person.class);
        p2.getAddress().setType("Office");
        System.out.println("p1=" + p1);
        System.out.println("p2=" + p2);
    }

@Data
public class Person{
    private String name;
    private Integer age;
    private Address address;
}

@Data
public class Address {
    private String type;
    private String value;
}

输出结果:

p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

利用Commons-BeanUtils复制对象

maven依赖

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.9.3</version>
</dependency>

测试用例:

@Test
    public void testCommonsBeanUtils(){
        Person p1=PersonFactory.newPrototypeInstance();
        try {
            Person p2=(Person) BeanUtils.cloneBean(p1);
            //p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
            System.out.println("p1=" + p1);
            p2.getAddress().setType("Office");
            //p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
            System.out.println("p2=" + p2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Orika复制对象

maven依赖

<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.0</version>
</dependency>

测试用例

@Test
public void testOrika() {
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    mapperFactory.classMap(Person.class, Person.class)
            .byDefault()
            .register();
    MapperFacade mapper = mapperFactory.getMapperFacade();

    Person p1=PersonFactory.newPrototypeInstance();
    Person p2 = mapper.map(p1, Person.class);
    System.out.println("p1=" + p1);
    p2.getAddress().setType("Office");
    System.out.println("p2=" + p2);
}

p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

相关文章