浅析原型模式中的clone()

更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)

最近学习到设计模式中的原型模式,在学习过程中,产生了对clone()实现的原理和效率的兴趣。

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

什么是clone(),和new有什么区别

clone()方法,在内存中进行数据块的拷贝,复制已有的对象,也是生成对象的一种方式。前提是类实现Cloneable接口,Cloneable接口没有任何方法,是一个空接口,也可以称这样的接口为标志接口,只有实现了该接口,才会支持clone()操作。类似这样的接口还有Serializable接口、RandomAccess接口等。

值得一提的是在执行clone操作的时候,不会调用构造函数。由于拷贝(浅拷贝)只是数据块的拷贝,所以自然不用调用构造函数,而new一个对象则需要构造函数来进行初始化。

浅拷贝和深拷贝

拷贝分为浅拷贝和深拷贝,浅拷贝是clone()的默认实现,对于需要拷贝的对象,浅拷贝只会拷贝基本类型属性的值,而引用类型属性拷贝的是引用地址

如上图,源对象有一个int类型的属性 “field1"和一个引用类型属性"refObj”,当对源对象浅拷贝时,新建了一个基本类型并拷贝,而拷贝对象和源对象的引用属性共用一份。下面是一个浅拷贝的例子:

public abstract class Robot implements Cloneable{
    protected Type type;
    private Integer id;

    public abstract void setType(Type type);
    public abstract Type getType();
    public abstract void work();

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("我们机器人中出了一个人类,类型不支持!");
        }
        return clone;
    }

而深拷贝则会完完全全按照源对象的值新建一个新对象。如下图:

要实现深拷贝,必须自己重写clone()方法,new一个新对象并将源对象的属性值传进去。下面是一个深拷贝的例子:

public abstract class Robot implements Cloneable{
    protected Type type;
    private Integer id;

    public abstract void setType(Type type);
    public abstract Type getType();
    public abstract void work();

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public Object clone() {
        // 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
        Robot clone = new SweepingRobot(type.getType(), id); 
        return clone;
    }

clone()一定比new快吗

通过对于深拷贝和浅拷贝的了解,我们已经知道,真正和new一个对象有原理上区别的是浅拷贝,而深拷贝和new其实没有什么区别。因此我们用浅拷贝和new两种实现分别来新建对象。


这是我们的实体类:

public class SweepingRobot extends Robot{
    @Override
    public void work() {
        System.out.println(getId()+"号扫地机器人开始工作。。。");
        System.out.println("清扫结束!");
    }

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public Type getType() {
        return type;
    }

    public SweepingRobot() {
        setType(Type.SWEEPING);
    }
}

这是我们的测试Demo:

public class Demo {
    public static void main(String[] args) {
        //浅拷贝
        long start = System.currentTimeMillis();
        RobotPrototype prototypes = RobotPrototype.PROTOTYPES;
        prototypes.loadPrototype();
        for (int i = 0; i < 10000; i++) {
            Robot robot = prototypes.getSweepingRobot();
            robot.work();
        }
        long end = System.currentTimeMillis();
        long time1 = end-start;
        //直接new
        start = System.currentTimeMillis();
        for (int i = 1; i <= 10000; i++) {
            Robot robot = new SweepingRobot();
            robot.setId(i);
            robot.work();
        }
        end = System.currentTimeMillis();
        long time2 = end-start;
        System.out.println("clone() = "+time1);
        System.out.println("new = "+time2);
    }
}

最终结果:

9996号扫地机器人开始工作。。。
清扫结束!
9997号扫地机器人开始工作。。。
清扫结束!
9998号扫地机器人开始工作。。。
清扫结束!
9999号扫地机器人开始工作。。。
清扫结束!
10000号扫地机器人开始工作。。。
清扫结束!
clone() = 81
new = 54

结果非但不像我们想的那样,甚至是new会更快一点,这是为什么呢?

回想clone()和new的最大区别是什么,是clone()不用调用构造函数,而我们的例子中构造函数基本没做什么事情,所以没节省什么时间。反倒是clone()操作,由于使用原型模式编写代码,使得代码结构变复杂,函数调用花费更多的时间,最终使得表现不如直接new。

那么接下来我们让构造函数多做点事:


只更改构造函数:

public class SweepingRobot extends Robot{
    @Override
    public void work() {
        System.out.println(getId()+"号扫地机器人开始工作。。。");
        System.out.println("清扫结束!");
    }

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public Type getType() {
        return type;
    }

    public SweepingRobot() {
        String temp = "123";
        for (int i = 0; i < 100; i++) {
            temp += i;
        }
        setType(Type.SWEEPING);
    }
}

最终结果:

9996号扫地机器人开始工作。。。
清扫结束!
9997号扫地机器人开始工作。。。
清扫结束!
9998号扫地机器人开始工作。。。
清扫结束!
9999号扫地机器人开始工作。。。
清扫结束!
10000号扫地机器人开始工作。。。
清扫结束!
clone() = 79
new = 104

由此可见,clone()不一定比new快,很多时候甚至会变慢。只有在对象类型比较复杂,构造函数所做事较多时,大量新建对象clone()会比new节省很多时间。

即轻量级对象直接使用new,重量级对象考虑使用clone();

热门文章

暂无图片
编程学习 ·

Android运行Linux程序

安卓直接运行arm-linux-gnueabi-gcc编译的标准嵌入式Linux程序,我们有时不想把原Linux程序重新开发一遍。第一步,给adb root权限运行,否则拷贝会提示无权限failed to copy E:\share\a8Agent1.0.1\a8Agent to /data/a8Agent: Permission deniedadb root 第二不,发送程序到安…
暂无图片
编程学习 ·

leetcode-123. 买卖股票的最佳时机 III

题目 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 示例 1: 输入: [3,3,5,0,0,3,1,4] 输出: 6 解释: 在第 4…
暂无图片
编程学习 ·

CMDB可用于那些服务和流程

CMDB可用于那些服务和流程CMDB不应该有哪些功能工单流程管理工单流程管理是一种流程管理手段,通过提交工单,逐级审批的方式,实现流程的流转,并可以提供回调Hook来自动执行某些操作。这样一个工单流程管理的功能,不仅需要对工单流程有详尽的了解,还需要对每个流程进行定制…
暂无图片
编程学习 ·

Shiro框架简单使用

文章目录1. Shiro过滤器&标签简介Shiro过滤器Shiro的JSP标签2. Shiro登陆认证(一)使用认证过滤器目标实现3. Shiro登陆认证(二)完成登录认证(*)目标总结4. Shiro登陆认证(三)凭证匹配器-普通加密需求步骤总结5. Shiro登陆认证(四)凭证匹配器-加盐加密需求什么是加…
暂无图片
编程学习 ·

Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的资源是否有更新。ajax 轮询在长时间不更新的情况下,反复地去询问会对服务器…
暂无图片
编程学习 ·

画图-身份证

画图函数:base_dir = f{main.BASE_DIR}/quality_management_logic/dataCenter/self.draw.text((55, self.height * 0.31), self.personName, (0, 0, 0),font=ImageFont.truetype(os.path.join(base_dir, msyh.ttc),24)) #personname应用: # -*- coding: utf-8 -*- # import …
暂无图片
编程学习 ·

Strategies For Pre-Training Graph Neural Networks

Paper : STRATEGIES FOR PRE-TRAINING GRAPH NEURAL NETWORKS Code : official摘要 作者解决的问题是如何预训练一个GNN网络,保证预训练的结果在具体数据集中finetune不会negative transfer 的现象。作者在文中并没有细致的解释为什么GNN上进行transfer learning 会更难,这个…
暂无图片
编程学习 ·

评估指标:精确率,召回率,F1_score,ROC,AUC

分类算法评估标准详解 分类准确度并不能够评估所有的场景,展示的结果也比较片面,这时候就需要其他的评估方法来进行测量评估。 所以接下来介绍一些其他的评估标准,将从以下5个方面来介绍: 混淆矩阵 精准率和召回率 F1 Score ROC曲线 AUC 一、混淆矩阵(Confusion Matrix) …
暂无图片
编程学习 ·

docsify 构建文档网站之定制功能(全网最全)

作者: wugenqiang 学习笔记:https://notebook.js.org/ 微信公众号:码客 E 分享(ID:enjoytoshare)文档后续更新地址:docsify 构建文档网站4 定制功能 文章目录4 定制功能4.1 支持 DOT 语言作图4.2 支持 LaTex 数学公式4.3 支持 PDF 页面展示4.4 支持回到顶部4.5 点击页面…
暂无图片
编程学习 ·

ubuntu如何远程连接和控制RS的仪表

RS有一台矢量信号源仪表 SMBV100B。需要通过安装了ubuntu的pc机局域网传matlab波形文件并设置频点等。需要3个步骤:1,安装ArbToolbox,用于将matlab波形转换成RS信号源识别的波形文件;https://www.rohde-schwarz.com.cn/applications/r-s-arb-toolbox-application-note_5628…
暂无图片
编程学习 ·

pyspark_聚合操作groupby_sum

print(*****************整体变化:) print(DF_temp.groupby().agg({deposit_increase:sum}).collect()) print(***************存款人均变化:) print(DF_temp.groupby().agg({deposit_increase:mean}).collect())
暂无图片
编程学习 ·

数据异常解决方法汇总

文章目录Step1:积极与需求方沟通Step2:将问题进行树枝细化,直至最小单元Step 3. 基于最小单元,梳理相关因素,进行猜想验证Step 4. 测算每个因素对结果的“贡献度”碰到实在分析不出原因的数据异常怎么办? 本文转载自公众号:数据分析师成长记录 Step1:积极与需求方沟通 数…
暂无图片
编程学习 ·

自以为是的炒股

2019,我回来了投机氛围分析得头头是道噢!GG了还是太年轻了建议:既然无法回本,那就及时止损!!! 投机氛围 进入到了新的公司,接触到了新的同事,当然少不了各种聊,八卦新闻,也会一不小心就聊到了投资理财(股票、基金神马的) 哈哈,还真是一朝被蛇咬,十年怕井绳! A:…
暂无图片
编程学习 ·

leetcode-面试题 17.11. 单词距离

问题: 有个内含单词的超大文本文件,给定任意两个单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗? 示例: 输入:words = ["I","am","a","student…
暂无图片
编程学习 ·

一个很小的错误,找半天,说明测试的话要完整测试.

如(d==.){//只能有1个点点数++;右();如(点数==1)下;打印("点数不对");置(m);中 0;}//不支持什么科学计数,这里就死循环了.就这么一块,少写了一个右().找半天.假设以后有时间写测试的话,一定要测试完整.将每个函数都测试到. 这样不会出错. 完整测试,不仅仅包括函数的完…
暂无图片
编程学习 ·

企业实战--kubernetes(八)---存储(ConfigMap)

一、Configmap简介 Configmap用于保存配置数据,以键值对的形式存储 Configmap资源提供了向Pod诸如配置数据的方法 旨在让镜像和配置文件解偶,以便实现镜像的可移植性和可复用性 典型的使用场景: 填充环境变量的值 设置容器内的命令行参数 填充卷的配置文件创建Configmap的方…
暂无图片
编程学习 ·

JavaScript按位运算符

按位运算符将其操作数当做32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值。 我们主要了解的有七种,分别是:&、|、^、~、<<、>>、>>>。 按位与运算符 按位与 & 运算符,对两个 32 位表达式的每一位执行按位与运算。如果a 和…