更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)
最近学习到设计模式中的原型模式,在学习过程中,产生了对clone()实现的原理和效率的兴趣。
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
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;
}
通过对于深拷贝和浅拷贝的了解,我们已经知道,真正和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();