面试题之浅克隆和深克隆

一、简介

在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()方法逻辑太多于复杂。

已标记关键词 清除标记
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值