浅析原型模式中的clone()

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

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

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

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

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

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

浅拷贝和深拷贝

拷贝分为浅拷贝和深拷贝,浅拷贝是clone()的默认实现,对于需要拷贝的对象,浅拷贝只会拷贝基本类型属性的值,而引用类型属性拷贝的是引用地址
浅析原型模式中的clone()_第1张图片
如上图,源对象有一个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()_第2张图片
要实现深拷贝,必须自己重写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();

你可能感兴趣的:(底层原理,设计模式,拷贝,新建对象)