结合JDK源码看设计模式——原型模式
定义:
指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。不需要知道任何创建的细节,不调用构造函数
适用场景:
- 类初始化的时候消耗较多资源
- new产生的对象需要非常繁琐的过程
- 构造函数比较复杂
- 循环体中产生大量对象
详解:
接下来我们分下面几部分讲解:
- 原型模式的核心
- 深克隆和浅克隆
- JDK源码分析
1.原型模式的核心
其实很简单,就是实现Cloneable接口,然后重写clone()方法。上面我们已经说过 ,当你在上面的适用场景中的时候,按照我们平常的办法来说肯定是直接new对象出来,但是new对象特别多的时候就会消耗很多资源,并且效率也是比较缓慢的。所以我们引入原型模式的情况,其实我们只需要创建出一个原型来 ,剩下的完全可以通过克隆来达到创建新对象的目的。克隆是底层直接拿二进制流来克隆出新对象,然后对新对象进行特别的操作。
2.深克隆和浅克隆
这时候你心里可能会有疑惑,克隆不就克隆就行了吗?怎么还分浅克隆和深克隆。事实上在一个类中如果有另外的类的实例作为属性的话,正常使用Object.clone()方法,这个对象成员是无法被克隆的,也就是浅克隆。所以你怎么改原型中的对象成员,后面克隆的版本中这个对象成员就会一直跟原型一样。但是我们的目标是创建新对象来进行特定的操作,也就是希望每个对象里面的值不会跟他人共享。新对象是新对象,原型是原型。所以我们需要深克隆。下面我举几个例子来看看
public class Student implements Cloneable{ private int age; private Date date; private String name; public void setAge(int age) { this.age = age; } public void setDate(Date date) { this.date = date; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", date=" + date + ", name='" + name + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
然后我们写一下测试类
class Test { public static void main(String[] args) throws CloneNotSupportedException { Student stu=new Student(); Student stu1= (Student) stu.clone(); Date date=new Date(0L); int i=10; stu.setDate(date); stu1.setDate(date); stu.setAge(i); stu1.setAge(i); System.out.println(stu); System.out.println(stu1); date.setTime(666666666L); stu.setDate(date); i=11; stu.setAge(i); System.out.println(stu); System.out.println(stu1); } }
最终输出结果就是
分析一下
注意看这4行,我们上面是两个都调用了set方法,下面只有stu原型调用了set方法,但是最终却两个对象中的值一起改了。可能细心的的注意到Age只有stu改了,这是因为int类型基本数据类型。而Date类型的对象成员就不行了,实际上只有Student这个类进行了克隆,但是Student里面的对象成员变量没有进行克隆。所以那个对象还是那个对象。
上面就是浅克隆。要想做到深克隆,可以在clone方法里面clone出对应的对象成员结果及代码如下
上面我重写了clone()方法,实现了深克隆
3.JDK源码解析
其实我们主要理解拷贝原型来创建新的对象,拷贝是比new更快的一个创建对象的方法,当你需要大批量创建新对象而且都是同一个类的对象的时候可以考虑用原型模式。但是千万千万注意就是一般的克隆只是浅克隆(浅克隆:只是对象的hash值不一样,但是对象里面的对象成员变量的hash值是一样的)有些场景可能是需要我们深克隆的,这时候就需要我们重写Object.clone()方法。就拿ArrayList中的clone()方法来看
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone();//先克隆出一个ArrayList v.elementData = Arrays.copyOf(elementData, size);//将原型中的数据拷贝到新的ArrayList中 v.modCount = 0;//把修改次数改为0 return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
相信看懂了上面的实例,你也能理解ArrayList中这段代码到底是做什么
总结:
尽管我们会用了原型,知道拷贝比new快,知道深克隆,但是具体的还是看业务场景的需求,希望能多理解适用场景的那几种情况。