面试官:你知道对象的克隆原理吗?

本文主要内容

背景

先说说生活中三个例子:

  • 西游记中,孙悟空能变身为n多个孙悟空,也就是一个孙悟空克隆为多个孙悟空了。

  • 王者农药中,元歌有个傀儡,这个傀儡我们也可理解为复制的元歌,你把傀儡杀死了,其实他自身根本没死。

  • 程序员把一份完整的代码复制成多分,每一份都是独立的一份完整的代码。

以上三个例子便是我们今天要聊的话题,克隆(复制)。

入门案例

在Java语言中也有这么一个复制的道理,请看下面的案例

    public class UserDto {
        private Long id;
        private String name;
        private Integer age;
        //set get省略
    }

下面我们进行复制:

    public class ObjectCloneDemo {
        public static void main(String[] args) {
            UserDto userDto = new UserDto();
            userDto.setAge(20);
            userDto.setId(100099L);
            userDto.setName("老田");
            //对象复制
            UserDto userDto1 = userDto;
            userDto1.setName(userDto.getName());
            userDto1.setId(userDto.getId());
            userDto1.setAge(userDto.getAge());
    
            userDto.setAge(22);
            userDto.setName("田维常");
            System.out.println(userDto);
            System.out.println(userDto1);
        }
    }

这种复制也叫引用复制。关系如下:

我们把前面创建的UserDto对象引用复制给userDto了,然后又对对象中的两个属性进行重新赋值,userDto1也会随着赋值的。这就是所谓的浅克隆(浅复制)。

关于对象复制,在实际开发中用的还是蛮多的,比如说:UserDto复制给UserVo或者UserBo。

什么是浅复制呢?

在浅复制中,如果原型对象的成员变量是值类型,将复制一份给目标对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给目标对象,也就是说原型对象和目标对象的成员变量指向相同的内存地址。

简单来说,在浅复制中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

在Java语言中,通过覆盖Object类的clone()方法加上实现Cloneable接口可以实现浅克隆。

前面说的孙悟空就是浅复制,因为你只要把原本的那个孙悟空干掉,其他也就不存在了,比如佛祖把孙悟空的原型按住,也就不存在多个孙悟空了。元歌的傀儡也是浅复制,因为我们把傀儡干掉了,对于原型的元歌来说根本就没有被干掉。

既然有浅复制,那么就会有深度复制吗?

是的。

案例

简单版,模仿用户信息,一个是用户地址类UserAddress和一个用户信息类User。

    public class UserAddress  {
        private int provinceCode;
        private int cityCode;
    
        public UserAddress() {
        }
    
        public UserAddress(int provinceCode, int cityCode) {
            this.provinceCode = provinceCode;
            this.cityCode = cityCode;
        }
    
        public int getProvinceCode() {
            return provinceCode;
        }
    
        public void setProvinceCode(int provinceCode) {
            this.provinceCode = provinceCode;
        }
    
        public int getCityCode() {
            return cityCode;
        }
    
        public void setCityCode(int cityCode) {
            this.cityCode = cityCode;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    public class User implements Cloneable{
        private int age;
        private String name;
        private UserAddress userAddress;
    
        public User() {
        }
    
        public User(int age, String name) {
            this.age = age;
            this.name = name;
        }
         //set get方法
        public static void main(String[] args) throws CloneNotSupportedException {
            User user = new User(20, "老田");
    
            UserAddress userAddress = new UserAddress();
            userAddress.setProvinceCode(100);
            userAddress.setCityCode(100100);
    
            user.setUserAddress(userAddress);
    
            User user1 = (User) user.clone();
            user1.getUserAddress().setCityCode(100101);
    
            user.setName("田维常");
    
            System.out.println(user.getName());
            System.out.println(user1.getName());
    
            System.out.println(user.getUserAddress() == user1.getUserAddress());
            System.out.println(user.getUserAddress().equals(user1.getUserAddress()));
    
            System.out.println(user.getUserAddress().getCityCode());
            System.out.println(user1.getUserAddress().getCityCode());
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

运行上面这段代码,输出结果为:

外面的User对象克隆是成功了,但是克隆出来的对象中,引用类型的属性并没有克隆出来,还是使用同一个引用地址。

什么是深度复制?

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现再加上实现Cloneable接口,也可以通过序列化(Serialization)等方来实现。

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。

关于序列化实现深度复制,请看这篇文章:面试官:说说你对序列化的理解

案例

下面使用Object的clone方法和实现Cloneable接口,写一个深度复制案例:

先创建一个用户地址类:

    public class UserAddress implements Cloneable {
        private int provinceCode;
        private int cityCode;
    
        public UserAddress() {
        }
        //set get 方法
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

再创建一个用户类:

    package com.tian.my_code.test.clone;
    
    public class User implements Cloneable {
        private int age;
        private String name;
        private UserAddress userAddress;
    
        public User() {
        }
    
        public User(int age, String name) {
            this.age = age;
            this.name = name;
        }
        //set get方法
        
        public static void main(String[] args) throws CloneNotSupportedException {
            User user = new User(20, "老田");
    
            UserAddress userAddress = new UserAddress();
            userAddress.setProvinceCode(100);
            userAddress.setCityCode(100100);
    
            user.setUserAddress(userAddress);
    
            User user1 = (User) user.clone();
            user1.setName("田维常");
            user1.getUserAddress().setCityCode(100101);
    
            System.out.println(user.getName());
            System.out.println(user1.getName());
    
            System.out.println(user == user1);
            System.out.println(user.equals(user1));
    
            System.out.println(user.getUserAddress() == user1.getUserAddress());
            System.out.println(user.getUserAddress().equals(user1.getUserAddress()));
    
            System.out.println(user.getUserAddress().getCityCode());
            System.out.println(user1.getUserAddress().getCityCode());
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            User user = (User) super.clone();
            user.setUserAddress((UserAddress) user.getUserAddress().clone());
            return user;
        }
    }
    

运行这段代码,输出结果为:

user和user1以及它们内部的引用属性都已经不是同一个了。这就是深度复制。

前面讲的程序拷贝代码,那就是深度复制,我们从githup拷贝一份代码到本地,我爱怎么改就怎么改,别人管不着,也不影响别人代码。

注意

Object 类的 clone 方法执行特定的复制操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。

所有的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我复制。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。

BeanUtils对象复制

Spring中

    org.springframework.beans.BeanUtils

这个工具类,使用频率还是蛮高的,那它的对象复制是深复制还是浅复制呢?

    public class UserAddress  {
        private int provinceCode;
        private int cityCode; 
        //set get方法
    }
    
    import org.springframework.beans.BeanUtils;
    public class User   {
        private int age;
        private String name;
        private UserAddress userAddress;
    
        public User() {
        }
    
        public User(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        
        public static void main(String[] args) throws CloneNotSupportedException {
            User user = new User(20, "老田");
    
            UserAddress userAddress = new UserAddress();
            userAddress.setProvinceCode(100);
            userAddress.setCityCode(100100);
    
            user.setUserAddress(userAddress);
    
            User user1 = new User();
            BeanUtils.copyProperties(user, user1);
            user1.getUserAddress().setCityCode(100101);
    
            user.setName("田维常");
    
            System.out.println(user.getName());
            System.out.println(user1.getName());
    
            System.out.println(user.getUserAddress() == user1.getUserAddress());
            System.out.println(user.getUserAddress().equals(user1.getUserAddress()));
    
            System.out.println(user.getUserAddress().getCityCode());
            System.out.println(user1.getUserAddress().getCityCode());
        } 
    }

运行这段代码,输出结果:

注意

User的属性userAddress还是使用同一个引用地址,所以属于浅复制。在工作中需要留意这个点。

原型模式

概述

模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

原型模式的目的就是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式的优缺点

优点:性能提高、逃避构造函数的约束。

缺点:

  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
  • 深度克隆时必须实现 Cloneable 接口和重写Object的clone方法,或者采取序列化方式。

与对象克隆的关系

对象的克隆或者复制就是原型模式的一种具体实现。

总结

何为浅克隆或浅复制?何为深克隆或深复制?实现深度复制的方式有哪些?如何实现?对象的赋值和原型模式有什么关联?

参考:cnblogs.com/fnlingnzb-learner/p/10649509.html

推荐阅读

7种启动Spring Boot项目的方式,一次性打包说给你听

6000多字 | 秒杀系统设计注意点【理论】

快速掌握模板方法模式

(0)

相关推荐

  • 设计模式-原型模式详解

    一.原型模式的概念 原型模式属于创建型设计模式.当要创建的对象类型由原型实例确定时使用它,该实例被克隆以生成新对象. 此模式用于 1.避免客户端应用程序中的对象创建者的子类,如工厂方法模式. 2.避免 ...

  • 设计模式-原型模式

    原型模式 ​原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,并且通过拷贝这些原型创建新的对象 ​调用者不需要知道任何创建细节,不调用构造函数 ​其属于一种创建型模式 通用类图 优点 性能 ...

  • 结合JDK源码看设计模式——原型模式

    定义: 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.不需要知道任何创建的细节,不调用构造函数 适用场景: 类初始化的时候消耗较多资源 new产生的对象需要非常繁琐的过程 构造函数比 ...

  • 大白话原型模式(Prototype Pattern)

    意图 原型模式是创建型设计模式,可以复制已存在的对象而无需依赖它的类. 问题 假如现在有一个对象,我们想完全复制一份新的,我们该如何做? 创建同一个类的新对象 遍历所有已存在对象的值,然后将他们的值复 ...

  • 原型模式

    序言:今天我们来聊一下原型模式,我个人认为原型模式的命名不太好理解,称呼其为克隆模式会更妥当一点.原型模式的目的是通过复制一个现有的对象来生成一个新的对象,而不是通过实例化的方法. 原型模式的基本介绍 ...

  • Photoshop-Ps-风格化干草文字-第1节

    教程未使用Adobe Photoshop最新版本,但其技术和过程仍然很重要. 1.基本图层和布局 第1步 打开素材图片,打开菜单,图像>图像大小,更改参数. 第2步 将原始图片作为智能对象导入. ...

  • 创建型模式之原型模式

    定义与特点 原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象. 在这里,原型实例指定了要创建的对象的种类.用这种方式创建对象 ...

  • 面试官:说说二维码扫码登录是什么原理吗?

    你知道的越多,不知道的就越多,业余的像一棵小草! 你来,我们一起精进!你不来,我和你的竞争对手一起精进! 编辑:业余草 在日常生活中,二维码出现在很多场景,比如超市支付.系统登录.应用下载等等.了解二 ...

  • android颜色!阿里P8面试官都说太详细了,源码 原理 手写框架

    前言 下面的题目都是楼主在Android交流群大家在面试字节跳动时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖. 参考解析:郭霖.鸿洋.玉刚.极客时间.腾讯课堂- 内容特点:条理 ...

  • 计算机网络原理 笔记精整理 对线面试官

    计算机网络知识点整理 一.计算机网络体系结构 OSI的七层协议体系结构的概念清除,理论也比较完整,但它既复杂又不实用. TCP/IP是一个四层体系结构,TCP/IP体系结构虽然简单,但它现在却得到了非 ...

  • 女面试官“品”加一笔,是什么字研究生无法回答,输给高中生

    对于面试,每个人都看得非常重要,因为这是人生中非常重要的一件大事,如果面试顺利,自己就可以去自己喜欢的公司上班,对自己未来的发展是非常有利的,但是想面试通过哪有那么简单,很多大企业招聘的岗位竞争都非常 ...

  • 面试官:“你能为公司创造什么价值”,犀利问题,这么回答就对了

    面试时,当面试官问问出"你能为公司带来什么"或"你认为公司为什么会录用你"时,实质就是在问员工的价值,回答得好,能够绝地反击,说服面试官,直接拿到offer. ...

  • 女面试官“九”字加一笔,是什么字研究生回答“丸”被淘汰

    现代企业越来越看重面试这一个环节了,以前很多只用考笔试的单位,现在都加入了面试这个环节,而且的话,面试环节的占比分似乎都已经达到了和笔试的分数持平的一个状态.而且现在的面试不仅考察学识方面的东西,而且 ...

  • 女面试官 我例假来了, 能帮我接杯热水吗 男子的回答当场录用

    女面试官:我例假来了,能帮我接杯热水吗?男子的回答当场录用现代社会最难的就是找工作,不光是应届毕业生,很多已经工作过几年的朋友,依然面临这重新找工作的问题,而且现在找工作之所以难,是因为它已经不是简单 ...

  • 遇到这样的面试官发Offer,接了你可别后悔

    前两周溜出去面试,和我之前提到的一样,即使自己还没有想跳槽的想法,也要出去面试,看看自己的市场价值是多少. 很多人在面试求职过程中,认为自己总是被动的,被选择的,其实面试是一个双向选择的过程,我们也在 ...

  • 面试官:什么东西越热越爱出来?硕士生机智回答被成功录取

    面试应该是现如今非常常见的事情了,不论你去哪里工作,都需要事先面试一下,看看你这个人到底适不适合这个工作或者岗位. 所以面试对你对公司都是非常重要的一步,其实除了大公司之外,一些小的或者是私人公司,面 ...