天天看点

Java对象的浅拷贝与深拷贝浅拷贝与深拷贝Java的clone方法深复制的实现

浅拷贝与深拷贝

浅拷贝(浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对拷贝对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制基本数据类型,而不复制引用类型的对象。

深拷贝(深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

Java的clone方法

(1)clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:

①对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象;

②对任何的对象x,都有x.clone().getClass()==x.getClass(),克隆对象与原对象的类型一样 ;

③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立;

④clone()方法是一个native方法,比new的执行效率高。

(2)Java中对象的克隆:

①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。

②在派生类中覆盖基类的clone()方法,并声明为public。

③在派生类的clone()方法中,调用super.clone()。

④在派生类中实现Cloneable接口。

package com.pechen.cloneable;
/**
 * 浅复制
 */
public class ShallowStudent implements Cloneable {
    private String name;
    private int age;
 
    ShallowStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public Object clone() {
        ShallowStudent o = null;
        try {
            o = (ShallowStudent) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
 
    public static void main(String[] args) {
        ShallowStudent s1 = new ShallowStudent("zhangsan", 18);
        ShallowStudent s2 = (ShallowStudent) s1.clone();
        s2.name = "lisi";
        s2.age = 20;
        //修改学生2后,不影响学生1的值。
        System.out.println("name=" + s1.name + "," + "age=" + s1.age);
        System.out.println("name=" + s2.name + "," + "age=" + s2.age);
    }
}
           

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。 JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

上面代码中有三个值得注意的地方,一是希望能实现clone功能的类必须实现Cloneable接口,这个接口属于java.lang包, java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。另一个值得请注意的是覆盖了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。

深复制的实现

①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之:

package com.pechen.cloneable;
/**
 * 浅复制2
 */
public class ShallowStudent2 implements Cloneable {
    String name;// 常量对象。
    int age;
    Professor p;// 学生1和学生2的引用值都是一样的。
    ShallowStudent2(String name, int age, Professor p) {
        this.name = name;
        this.age = age;
        this.p = p;
    }
 
    public Object clone() {
        ShallowStudent2 o = null;
        try {
            o = (ShallowStudent2) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
 
    public static void main(String[] args) {
        Professor p = new Professor("wangwu", 50);
        ShallowStudent2 s1 = new ShallowStudent2("zhangsan", 18, p);
        ShallowStudent2 s2 = (ShallowStudent2) s1.clone();
        s2.p.name = "lisi";
        s2.p.age = 30;
        System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age);
        System.out.println("name=" + s2.p.name + "," + "age=" + s2.p.age);
        //输出结果学生1和2的教授成为lisi,age为30。
    }
}
 
class Professor {
    String name;
    int age;
    Professor(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
           

调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为”影子clone”。

那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?代码改进如下。 改进使学生1的Professor不改变(深层次的克隆):

package com.pechen.cloneable;
/**
 * 深复制
 */
public class DeepStudent implements Cloneable {
    String name;
    int age;
    DeepProfessor p;// 学生1和学生2的引用值都是一样的。
    DeepStudent(String name, int age, DeepProfessor p) {
        this.name = name;
        this.age = age;
        this.p = p;
    }
 
    public Object clone() {
        DeepStudent o = null;
        try {
            o = (DeepStudent) super.clone();
            //对引用的对象也进行复制
            o.p = (DeepProfessor) p.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
 
    public static void main(String[] args) {
        DeepProfessor p = new DeepProfessor("wangwu", 50);
        DeepStudent s1 = new DeepStudent("zhangsan", 18, p);
        DeepStudent s2 = (DeepStudent) s1.clone();
        s2.p.name = "lisi";
        s2.p.age = 30;
        System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age);
        System.out.println("name=" + s2.p.name + "," + "age=" + s2.p.age);
        //输出结果学生1和2的教授成为lisi,age为30。
    }
}
 
class DeepProfessor implements Cloneable {
    String name;
    int age;
    DeepProfessor(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Object clone() {
        DeepProfessor o = null;
        try {
            o = (DeepProfessor)super.clone();
        } catch(CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
}
           

通过以上例子我们可以看出在某些情况下,我们可以利用clone方法来实现对象的深度复制,但对于比较复杂的对象(比如对象中包含其他对象,其他对象又包含别的对象…..)这样我们必须进行层层深度clone,每个对象需要实现cloneable接口,比较麻烦,这个时候就需要使用对象序列化了。