面试题之浅克隆和深克隆

一、简介

在Object基类中,有一个方法叫clone,产生一个原始对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。

  • clone()方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,没有实现Cloneable接口,那么调用clone方法就会报错CloneNotSupportedException异常。
  •  
  • Object类中的clone方法是protected修饰的,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。
protected native Object clone() throws CloneNotSupportedException;


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

二、浅克隆

浅克隆就是引用类型的属性无法完全复制,类User中包含成绩属性Mark,Mark是由Chinese和math等等组成的,浅克隆示例:

/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher {
    private Integer pkid;
    private String name;
 
    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

package com.wsh.springboot.springbootdesignpattern.prototypepattern.shallowclone;
 
/**
 * @Description:
 * @author: weishihuai
 * @Date: 2019/10/22 20:11
 */
public class Student implements Cloneable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;
 
    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }
 
    public double getWeight() {
        return weight;
    }
 
    public void setWeight(double weight) {
        this.weight = weight;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Teacher getTeacher() {
        return teacher;
    }
 
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
 
    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return object;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.clone();
 
        System.out.println(student == student1);
 
        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());
 
 
        System.out.println("克隆之前student学生的老师: " + student.getTeacher().getName());
        //克隆对象student1将老师姓名修改为张老师,导致旧student的老师姓名也被修改。
        Teacher teacher1 = student1.getTeacher();
        teacher1.setName("张老师");
        System.out.println("克隆之后student学生的老师: " + student.getTeacher().getName());
 
        System.out.println("克隆之后的新学生的老师: " + student.getTeacher().getName());
    }
}

输出结果:

可见浅拷贝之后的student对象与原来的对象是不同的对象,但是student对象里面的teacher对象与原teacher还是指向的同一个地址,旧对象修改会导致新对象也被修改,这就是浅拷贝的缺点,浅拷贝在克隆基本数据类型的时候比较常见。

特殊情况:属性是String的情况,String也是一个类,那String是引用类型吗?String的表现有的像基本类型,归根到底就是因为String不可变性,克隆之后两个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。

三、深克隆

  • 方式一:clone函数的嵌套调用
public class Student implements Cloneable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;
 
    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }
 
    public double getWeight() {
        return weight;
    }
 
    public void setWeight(double weight) {
        this.weight = weight;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Teacher getTeacher() {
        return teacher;
    }
 
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
 
    @Override
    protected Object clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
            student.teacher = (Teacher) this.teacher.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return student;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher implements Cloneable {
    private Integer pkid;
    private String name;
 
    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
 
    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return object;
    }
}

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.clone();
 
        System.out.println(student);
        System.out.println(student1);
 
        System.out.println(student == student1);
        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());
    }
}

可见,复制之后的student对象以及对应的teacher对象都是全新的,与旧对象互不影响,这就是深克隆。深拷贝关键点在于,实现cloneable接口以及用object的clone方法。

  • 方式二:序列化

序列化方式只需要给每个类都实现一个Serializable接口,也是标记接口,最后同序列化和反序列化操作达到克隆的目的(包括数组的复制):

import java.io.Serializable;
 
/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher implements Serializable {
    private Integer pkid;
    private String name;
 
    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
 
}

import java.io.*;
 
/**
 * @Description:
 * @author: weishihuai
 * @Date: 2019/10/22 20:11
 */
public class Student implements Serializable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;
 
    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }
 
    public double getWeight() {
        return weight;
    }
 
    public void setWeight(double weight) {
        this.weight = weight;
    }
 
    public Integer getPkid() {
        return pkid;
    }
 
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Teacher getTeacher() {
        return teacher;
    }
 
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
 
 
    public Student deepCloneBySerializable() {
        Student student = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);
 
            byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            student = (Student) objectInputStream.readObject();
            return student;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (null != objectInputStream) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != byteArrayInputStream) {
                try {
                    byteArrayInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != objectOutputStream) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != byteArrayOutputStream) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return student;
    }
}

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.deepCloneBySerializable();
 
        System.out.println(student);
        System.out.println(student1);
 
        System.out.println(student == student1);
        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());
    }
}

这就是使用序列化方式深拷贝对象,推荐使用此种方式实现深拷贝,避免重写clone()方法逻辑太多于复杂。

发布了197 篇原创文章 · 获赞 86 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/103722428