Object.clone()方法与对象的深浅拷贝

时间:2022-12-04 19:52:28

转载:【https://www.cnblogs.com/nickhan/p/8569329.html】

引言

  在某些场景中,我们需要获取到一个对象的拷贝用于某些处理。这时候就可以用到Java中的Object.clone方法进行对象复制,得到一个一模一样的新对象。但是在实际使用过程中会发现:当对象中含有可变的引用类型属性时,在复制得到的新对象对该引用类型属性内容进行修改,原始对象响应的属性内容也会发生变化,这就是"浅拷贝"的现象。关于浅拷贝,Object.clone()方法的描述也有说明: 

一、浅拷贝

  接下来用代码看看浅拷贝的效果、创建两个类ClassA、ClassB

public class ClassA implements Cloneable {

    private String classNameA ;

    private Integer ageA;

    private ClassB classB;

    public ClassA(Integer age, String classNameA, ClassB classB){
        this.classNameA = classNameA;
        this.ageA = age;
        this.classB = classB;
    }

    public String getClassNameA() {
        return classNameA;
    }

    public void setClassNameA(String classNameA) {
        this.classNameA = classNameA;
    }

    public ClassB getClassB() {
        return classB;
    }

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public Integer getAge() {
        return ageA;
    }

    public void setAge(Integer ageA) {
        this.ageA = ageA;
    }

    protected Object clone() {
        try{
            return super.clone();
        }catch(Exception e){
            return null;
        }
    }
}
public class ClassB {

    private String classNameB;

    private Integer ageB;

    public ClassB(Integer ageB, String classNameB){
        this.ageB = ageB;
        this.classNameB = classNameB;
    }

    public String getClassNameB() {
        return classNameB;
    }

    public void setClassNameB(String classNameB) {
        this.classNameB = classNameB;
    }

    public Integer getAgeB() {
        return ageB;
    }

    public void setAgeB(Integer ageB) {
        this.ageB = ageB;
    }
}

 测试代码:

public class Test {
    public static void main(String[] args) throws IOException {
        ClassA classA1 = new ClassA(22,"逝清雪", new ClassB(24,"莫问"));
        ClassA classA2 = (ClassA) classA1.clone();

        System.out.println(classA1 == classA2);
        System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
        System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
        System.out.println("-------------------------------------------------------------------");

        //改变克隆对象的基本类型变量
        classA2.setAge(100);
        classA2.setClassNameA("凌雪伤");
        System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
        System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
        System.out.println("-------------------------------------------------------------------");

        //改变克隆对象的引用对象的值
        classA2.getClassB().setClassNameB("天下无双");
        classA2.getClassB().setAgeB(120);
        System.out.println("classA1" + "-----" + classA1.getClassB().getAgeB() + "-----" + classA1.getClassB().getClassNameB());
        System.out.println("classA2" + "-----" + classA2.getClassB().getAgeB() + "-----" + classA2.getClassB().getClassNameB());


    }
}

  测试结果:

1 false
2 classA1-----22-----逝清雪
3 classA2-----22-----逝清雪
4 -------------------------------------------------------------------
5 classA1-----22-----逝清雪
6 classA2-----100-----凌雪伤
7 -------------------------------------------------------------------
8 classA1-----120-----天下无双
9 classA2-----120-----天下无双

  可以看到打印结果:

    第1句输出说明了原对象与新对象是两个不同的对象:

    第2、3句可以看到拷贝出来的新对象与原对象内容一致:

    第5、6句重新设置了拷贝对象的值,答应结果也和预期一样正确输出:

    第8、9句重新设置了拷贝对象中引用变量的值,然而打印结果中原对象中的信息也跟着变化了,仔细观察发现原对象跟着变化的只是classB部分,这就跟clone本身的浅拷贝有关系了。 

  浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象,因此,修改classA2里面的ClassB内容时,原classA1里面的ClassB内容会跟着改变。

  了解了浅拷贝,那么深拷贝是什么也就很清楚了。即将引用类型的属性内容也拷贝一份新的。那么,实现深拷贝我这里收集到两种方式:第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化。接下来分别对两种方式进行演示。

二、深拷贝:

  1、深拷贝-clone方式:

    对于以上演示代码,利用clone方式进行深拷贝无非就是将ClassB类也实现Cloneable,然后对ClassA的clone方法进行调整,下面对ClassA、ClassB做一下调整:

public class ClassA implements Cloneable {

    private String classNameA ;

    private Integer ageA;

    private ClassB classB;

    public ClassA(Integer age, String classNameA, ClassB classB){
        this.classNameA = classNameA;
        this.ageA = age;
        this.classB = classB;
    }

    public String getClassNameA() {
        return classNameA;
    }

    public void setClassNameA(String classNameA) {
        this.classNameA = classNameA;
    }

    public ClassB getClassB() {
        return classB;
    }

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public Integer getAge() {
        return ageA;
    }

    public void setAge(Integer ageA) {
        this.ageA = ageA;
    }

    protected Object clone() {
        try{
            ClassA classA = (ClassA)super.clone();
            //手动对address属性进行clone,并赋值给新的person对象
            classA.classB = (ClassB)classB.clone();
            return super.clone();
        }catch(Exception e){
            return null;
        }
    }
}
public class ClassB implements Cloneable{

    private String classNameB;

    private Integer ageB;

    public ClassB(Integer ageB, String classNameB){
        this.ageB = ageB;
        this.classNameB = classNameB;
    }

    public String getClassNameB() {
        return classNameB;
    }

    public void setClassNameB(String classNameB) {
        this.classNameB = classNameB;
    }

    public Integer getAgeB() {
        return ageB;
    }

    public void setAgeB(Integer ageB) {
        this.ageB = ageB;
    }

    protected Object clone() {
        try{
            return super.clone();
        }catch(Exception e){
            return null;
        }
    }
}

  测试结果:

false
classA1-----22-----逝清雪
classA2-----22-----逝清雪
-------------------------------------------------------------------
classA1-----22-----逝清雪
classA2-----100-----凌雪伤
-------------------------------------------------------------------
classA1-----24-----莫问
classA2-----120-----天下无双

    可以看到原ClassA1对象的ClassB内容未受到影响。

    但是,这种方法的缺点就是当一个类里面有很多引用类型时,需要手动调用很多clone,而且如果引用类型内部还有引用类型时,那么代码将会很恶心,量也很大。。。

    所以这种方式一般用于引用类型变量较少的时候对于很多引用类型,可以使用序列化对象的方式进行深拷贝。

  2、深拷贝-序列化方式

    这种方式其实就是将对象转成二进制流,然后再把二进制流反序列成一个java对象,这时候反序列化生成的对象是一个全新的对象,里面的信息与原对象一样,但是所有内容都是一份新的,这种方式需要注意的地方主要是所有类都需要实现Serializable接口,以便进行序列化操作。

public class DeepClone implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 利用序列化和反序列化进行对象的深拷贝
     * @return
     * @throws Exception
     */
    protected Object deepClone() throws Exception{
        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        
        oos.writeObject(this);
        
        //反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}
public class ClassA extends DeepClone {
    private static final long serialVersionUID = 1L;

    private String classNameA ;

    private Integer ageA;

    private ClassB classB;

    public ClassA(Integer age, String classNameA, ClassB classB){
        this.classNameA = classNameA;
        this.ageA = age;
        this.classB = classB;
    }

    public String getClassNameA() {
        return classNameA;
    }

    public void setClassNameA(String classNameA) {
        this.classNameA = classNameA;
    }

    public ClassB getClassB() {
        return classB;
    }

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public Integer getAge() {
        return ageA;
    }

    public void setAge(Integer ageA) {
        this.ageA = ageA;
    }

}
public class ClassB extends DeepClone{
    private static final long serialVersionUID = 1L;

    private String classNameB;

    private Integer ageB;

    public ClassB(Integer ageB, String classNameB){
        this.ageB = ageB;
        this.classNameB = classNameB;
    }

    public String getClassNameB() {
        return classNameB;
    }

    public void setClassNameB(String classNameB) {
        this.classNameB = classNameB;
    }

    public Integer getAgeB() {
        return ageB;
    }

    public void setAgeB(Integer ageB) {
        this.ageB = ageB;
    }

}

  测试代码:

public class Test {
    public static void main(String[] args) throws Exception {
        ClassA classA1 = new ClassA(22,"逝清雪", new ClassB(24,"莫问"));
        ClassA classA2 = (ClassA)classA1.deepClone();

        System.out.println(classA1 == classA2);
        System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
        System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
        System.out.println("-------------------------------------------------------------------");

        //改变克隆对象的基本类型变量
        classA2.setAge(100);
        classA2.setClassNameA("凌雪伤");
        System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
        System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
        System.out.println("-------------------------------------------------------------------");

        //改变克隆对象的引用对象的值
        classA2.getClassB().setClassNameB("天下无双");
        classA2.getClassB().setAgeB(120);
        System.out.println("classA1" + "-----" + classA1.getClassB().getAgeB() + "-----" + classA1.getClassB().getClassNameB());
        System.out.println("classA2" + "-----" + classA2.getClassB().getAgeB() + "-----" + classA2.getClassB().getClassNameB());

    }
}

  测试结果:

false
classA1-----22-----逝清雪
classA2-----22-----逝清雪
-------------------------------------------------------------------
classA1-----22-----逝清雪
classA2-----100-----凌雪伤
-------------------------------------------------------------------
classA1-----24-----莫问
classA2-----120-----天下无双

Process finished with exit code 0

  可见,对新对象用序列化的方式也可以实现深拷贝,这便是由Object.clone()引出的深拷贝与浅拷贝,学习记录一下。