Java的浅克隆和深克隆

克隆就是对象复制的过程,而生成的副本与被复制的对象只是值相等而不是真正意义上的同一个对象。而我们平时通过更改对象的引用来复制对象,这种方法的两个对象引用其实都指向堆中的同一块内存,是真正意义上的同一个对象,举个例子来说,

        Person person1 = new Person();
        person1.setName("小明");
        person1.setAge(18);

        Person person2 = person1;
        person2.setName("小丽");

        System.out.println("person1 的名字为: "+person1.getName());
        System.out.println("person2 的名字为: "+person2.getName());

打印结果如下:


image.png

改变person2的name,竟然连person1的name一起改了,实际上person1和person2在内存中表现形式如下:


Java的浅克隆和深克隆_第1张图片
image.png

person1和person2是指向同一对象的,所有更改了任何一个对象的属性,则都会同步到另外一个对象。
如果只是想获取一个对象的副本,就可以通过克隆的方式
  /*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;

查看源码,大致意思如下

  • 原始和克隆两者不是同一个对象
  • 原始和克隆的对象应该具有相同的类类型,但它不是强制性的
  • 原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的

浅克隆

因为每个类都是直接或者间接的继承Object类,如果想要实现对象的clone,则必须实现Cloneable接口。在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制(严格来说是对象地址的复制)。

public class Person implements Cloneable {

    private String name;
    private int age;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }


    @Override
    public Object clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return person;
    }

}

打印结果如下:


image.png

当原始对象含引用类型时,

public class Person implements Cloneable {

    private String name;
    private int age;
    private Student student;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Student getStudent() {
        return student;
    }

    @Override
    public Object clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return person;
    }
    public static void main(String[] args) {

        Person person1 = new Person();
        Student student = new Student();
        student.setHeight(180);
        person1.setStudent(student);
        person1.setName("小明");
        person1.setAge(18);

        Person person2 = (Person) person1.clone();
        person2.getStudent().setHeight(200);

        System.out.println(person1.getStudent() == person2.getStudent());
        System.out.println("person1 高度为: " + person1.getStudent().getHeight());
        System.out.println("person2 高度为: " + person2.getStudent().getHeight());

    }

打印结果如下:


Java的浅克隆和深克隆_第2张图片
image.png

这里还是发生了开头的那个问题。对象的属性被篡改了,并且student对象是同一个对象,这是因为我们并未对student对象进行同步克隆的原因。

深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
下面对student进行克隆修改如下:

public class Student implements Cloneable {

    private int height;

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public Object clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return student;
    }
}
public class Person implements Cloneable {

    private String name;
    private int age;
    private Student student;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Student getStudent() {
        return student;
    }

    @Override
    public Object clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
            person.setStudent((Student)student.clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return person;
    }

这时候再去运行上面的测试例子,打印结果如下:


jiu'de'dao'wo'men

就得到了我们想要的结果了。
还要考虑一种情况,当成员变量为引用类型时,并且引用类型的成员变量也为引用类型时,这时候就需要每个引用类型都要实现克隆接口,折个工作量就很大了,有没有一种很简单的方式呢?答案肯定是有的,我们可以使用对象序列化的方式来实现。

public class Person implements Serializable {

    private static final long serialVersionUID = -71293312083100779L;

    private String name;
    private int age;
    private Student student;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Student getStudent() {
        return student;
    }

    public Person myClone() {
        Person person = null;
        try {
            // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝   
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            // 将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            person = (Person) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return person;
    }
public class Student implements Serializable {

    private static final long serialVersionUID = 503951312982338998L;

    private int height;

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}

在测试代码中调用myClone方法创建person2,打印结果如下:


image.png

这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

综合上述对象实现克隆有两种方法:

    1. 实现Cloneable接口。
    1. 实现序列化。

你可能感兴趣的:(Java的浅克隆和深克隆)