浅析原型模式中的clone()

    技术2022-07-10  118

    更多精彩文章请访问我的个人博客(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();

    Processed: 0.010, SQL: 9