一、出现原因

在项目中经常需要复制一个完全一样的对象,然后再对新对象进行更新等操作而不影响老对象。

而以以下方式获取是否会出现问题呢?

User user = new User();
User copyUser = user;

答案是肯定的,上面的方法不能称之为复制对象,更准确地说应该是复制引用,因为user和copyUser指向的是内存堆里的同一个对象:

user                 ¦→      Object

copyUser         ¦→          ↑

栈区(引用)   ¦    堆区(对象)

 

那么如何才能实现呢?应该使用Java中的拷贝(Object  Copy),主要分为:浅拷贝 (Shallow Copy)、深拷贝 (Deep Copy),用的方法为clone()。

 

二、浅拷贝与深拷贝的区别

1、浅拷贝:

类实现默认的Object.clone()方法,拷贝对象时,

(1)对于引用类型的成员变量(属性)拷贝只是拷贝“值”即地址(引用),没有在堆中开辟新的内存空间;

(2)对于字段类型是值类型(基本类型)的,那么对该字段进行复制。

2、深拷贝:

类重写clone()方法,对于引用类型成员变量,重新在堆中开辟新的内存空间,简单地说,将对象的全部属性内容都拷贝一份新的。

3、使用:

如果复制对象只含基本数据类型,则使用浅拷贝即可满足;若果包含成员对象,则需使用深拷贝。

 

三、浅拷贝

一般实现方法:

  1. 被复制的类需要实现 Clonenable 接口(不实现的话在调用 clone 方法会抛出 CloneNotSupportedException 异常) 该接口为标记接口 (不含任何方法)
  2. 覆盖 clone () 方法,方法中调用 super.clone () 方法得到需要的复制对象,(native 为本地方法)
public class Person implements Cloneable{
    private  String name;
    private  int age;
    private Address address;

    //构造方法
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public  String display(){
        return "Person[name="+name+",age="+age+",address"+address+"]";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}



public class Address {
    private String province;//省份
    private String city;//所在城市

    //构造方法
    public Address(String province, String city) {
        this.province = province;
        this.city = city;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address[province="+province+",city="+city+"]";
    }
}



public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person=new Person("张三",20,new Address("安徽","合肥"));

        Person clonePerson=(Person) person.clone();

        System.out.println(person);
        //com.hand.hand.clone.Person@1d81eb93
        System.out.println(clonePerson);
        //com.hand.hand.clone.Person@1d81eb93

        System.out.println(person.display());
        //Person[name=张三,age=20,addressAdderss[provice=安徽,city=合肥]]
        System.out.println(clonePerson.display());
        //Person[name=张三,age=20,addressAdderss[provice=安徽,city=合肥]]


     //证明String的特殊性,因为String是final的,所以会开辟新的内存空间
        clonePerson.setName("李四");
     //证明基本数据类型是被拷贝是开辟新空间的
        clonePerson.setAge(22);
     //证明成员变量所指向的是同一个对象
        Address address=clonePerson.getAddress();
        address.setProvince("江苏");

        System.out.println(person.display());
        //Person[name=张三,age=20,addressAdderss[provice=江苏,city=合肥]]
        System.out.println(clonePerson.display());
        //Person[name=李四,age=22,addressAdderss[provice=江苏,city=合肥]]
    }
}

 

 

四、深拷贝

 针对以上成员变量无法完成拷贝的情况出现了深拷贝。

实现方式有以下两种:

  • 第一种

与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法,最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝 = 深拷贝。

  • 第二种

结合序列化来解决这个问题,先把对象序列化,然后再反序列化成对象,该对象保证每个引用都是崭新的。这个就形成了多个引用,原引用和反序列化之后的引用不在相同,具体实现:

public class Student implements Cloneable, Serializable {
    private static final long serialVersionUID = 1L;

    private int age;
    private Address address;

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    @Override
    protected Object clone() {
        Student stu = null;
        try {
            // 将对象写成byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 从流中读出byte array
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            stu = (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stu;
    }
}



public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        Address address = new Address();
        address.setAddress("beijing");
        stu1.setAge(20);
        stu1.setAddress(address);
        Student stu2 = (Student) stu1.clone();

        address.setAddress("jinan");
        stu2.setAge(10);

        System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
        //20jinan
        System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
        //10beijing

        stu2.setAddress(address);
        System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
        //20jinan
        System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
        //10jinan
    }
}