作者:zuoxiaolong8810(左潇龙),转载请注明出处。
原型模式算是Java中最简单的设计模式了,原因是因为它已经被提供了语言级的支持,但是如果提到它的实现原理,又是最复杂的一个设计模式。
下面我们先来看看这个又简单又复杂的设计模式的定义。
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
定义比较简单,总结一下是通过实例指定种类,通过拷贝创建对象。
在JAVA语言中使用原型模式是非常简单的,这是因为Object类当中提供了一个本地方法clone,而JAVA中的任何类只要实现了Cloneable标识接口,就可以使用clone方法来进行对象的拷贝。
我们写一个简单的实例来测试一下,很简单。
- package com.prototype;
-
- public class Prototype implements Cloneable {
-
- private int x;
- private int y;
- private int z;
-
- public Prototype() {
- this.x = 2;
- this.y = 3;
- this.z = 4;
- }
-
- public void change() {
- this.x = 9;
- this.y = 8;
- this.z = 7;
- }
-
- public Prototype clone() {
- Object object = null;
- try {
- object = super.clone();
- } catch (CloneNotSupportedException exception) {
- throw new RuntimeException(exception);
- }
- return (Prototype) object;
- }
-
- public String toString() {
- return "[" + x + "," + y + "," + z + "]";
- }
-
- public static void main(String[] args) {
- Prototype prototype1 = new Prototype();
- prototype1.change();
- System.out.println(prototype1);
- Prototype prototype2 = prototype1.clone();
- System.out.println(prototype2);
- }
-
- }
输入结果:
[9,8,7]
[9,8,7]
从输出结果可以看出来,clone方法将prototype1复制了一个,然后赋给了prototype2,这就像复制粘贴一样。值得注意的是,在使用Object.clone()方法去拷贝一个对象时,构造方法是不被执行的,否则prototype2实例中x,y,z的值应该为2,3,4才对,如果你觉得不够直观,可以在构造方法里写一个输出语句试试。
从原型模式的使用方式不难推断出,原型模式常使用于以下场景:
1、对象的创建非常复杂,可以使用原型模式快捷的创建对象。
2、在运行过程中不知道对象的具体类型,可使用原型模式创建一个相同类型的对象,或者在运行过程中动态的获取到一个对象的状态。
对于clone方法,它执行的是浅拷贝,也就是说如果是引用类型的属性,则它不会进行拷贝,而是只拷贝引用。
看下面这个简单的测试,就能看出来了。
- package com.prototype;
-
- class Field{
-
- private int a;
-
- public int getA() {
- return a;
- }
-
- public void setA(int a) {
- this.a = a;
- }
-
- }
-
- public class ShallowPrototype implements Cloneable {
-
- private int x;
- private int y;
- private int z;
- private Field field;
-
- public ShallowPrototype() {
- this.x = 2;
- this.y = 3;
- this.z = 4;
- this.field = new Field();
- this.field.setA(5);
- }
-
- public Field getField() {
- return field;
- }
-
- public ShallowPrototype clone() {
- Object object = null;
- try {
- object = super.clone();
- } catch (CloneNotSupportedException exception) {
- throw new RuntimeException(exception);
- }
- return (ShallowPrototype) object;
- }
-
- public String toString() {
- return "[" + x + "," + y + "," + z + "," + field.getA() + "]";
- }
-
- public static void main(String[] args) {
- ShallowPrototype prototype1 = new ShallowPrototype();
- System.out.println(prototype1);
- System.out.println(prototype1.getField());
- ShallowPrototype prototype2 = prototype1.clone();
- System.out.println(prototype2);
- System.out.println(prototype2.getField());
- }
-
- }
输入结果:
[2,3,4,5]
com.prototype.Field@de6ced
[2,3,4,5]
com.prototype.Field@de6ced
可以看到我们对ShallowPrototype拷贝以后,得到一个实例prototype2,不过当我们输出field属性时,发现它们是引用的同一个对象。这当然不是我们期望得到的结果,这种情况下,我们如果修改prototype1中field的属性a的值,则prototype2中的也会跟着改变。
然而如果要实现深度拷贝,则需要将实现了Cloneable接口并重写了clone方法的类中,所有的引用类型也全部实现Cloneable接口并重写clone方法,而且需要将引用类型的属性全部拷贝一遍。
下面是一个简单的深度拷贝的例子,由上面的例子更改得到。
- package com.prototype;
-
- class Field implements Cloneable{
-
- private int a;
-
- public int getA() {
- return a;
- }
-
- public void setA(int a) {
- this.a = a;
- }
-
- protected Field clone() {
- Object object = null;
- try {
- object = super.clone();
- } catch (CloneNotSupportedException exception) {
- throw new RuntimeException(exception);
- }
- return (Field) object;
- }
-
- }
-
- public class DeepPrototype implements Cloneable {
-
- private int x;
- private int y;
- private int z;
- private Field field;
-
- public DeepPrototype() {
- this.x = 2;
- this.y = 3;
- this.z = 4;
- this.field = new Field();
- this.field.setA(5);
- }
-
- public Field getField() {
- return field;
- }
-
- protected DeepPrototype clone() {
- Object object = null;
- try {
- object = super.clone();
- ((DeepPrototype)object).field = this.field.clone();
- } catch (CloneNotSupportedException exception) {
- throw new RuntimeException(exception);
- }
- return (DeepPrototype) object;
- }
-
- public String toString() {
- return "[" + x + "," + y + "," + z + "," + field.getA() + "]";
- }
-
- public static void main(String[] args) {
- DeepPrototype prototype1 = new DeepPrototype();
- System.out.println(prototype1);
- System.out.println(prototype1.getField());
- DeepPrototype prototype2 = prototype1.clone();
- System.out.println(prototype2);
- System.out.println(prototype2.getField());
- }
-
- }
输出结果:
[2,3,4,5]
com.prototype.Field@a90653
[2,3,4,5]
com.prototype.Field@de6ced
下面我们来看下原型模式的主要优点:
1、由于clone方法是由虚拟机直接复制内存块执行,所以在速度上比使用new的方式创建对象要快。
2、可以基于原型,快速的创建一个对象,而无需知道创建的细节。
3、可以在运行时动态的获取对象的类型以及状态,从而创建一个对象。
然而原型模式的缺点也是相当明显的,主要的缺点就是实现深度拷贝比较困难,需要很多额外的代码量。
不过实际当中我们使用原型模式时,也可以写一个基类实现Cloneable接口重写clone方法,然后让需要具有拷贝功能的子类继承自该类,这是一种节省代码量的常用方式。像上面的例子一样,如果一个类继承自Prototype,则会自动具有拷贝功能。
下面我们来看看虚拟机中本地方法Object.clone()的源代码,如下。
- JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
- JVMWrapper("JVM_Clone");
- Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
- const KlassHandle klass (THREAD, obj->klass());
- JvmtiVMObjectAllocEventCollector oam;
-
- #ifdef ASSERT
-
- if (obj->is_javaArray()) {
- guarantee(klass->is_cloneable(), "all arrays are cloneable");
- } else {
- guarantee(obj->is_instance(), "should be instanceOop");
- bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
- guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
- }
- #endif
-
-
-
- if (!klass->is_cloneable()) {
- ResourceMark rm(THREAD);
- THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
- }
-
-
- const int size = obj->size();
- oop new_obj = NULL;
- if (obj->is_javaArray()) {
- const int length = ((arrayOop)obj())->length();
- new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
- } else {
- new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
- }
-
-
-
-
-
-
-
-
-
- assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
- Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
- (size_t)align_object_size(size) / HeapWordsPerLong);
-
- new_obj->init_mark();
-
-
- BarrierSet* bs = Universe::heap()->barrier_set();
- assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
- bs->write_region(MemRegion((HeapWord*)new_obj, size));
-
-
-
- if (klass->has_finalizer()) {
- assert(obj->is_instance(), "should be instanceOop");
- new_obj = instanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);
- }
-
- return JNIHandles::make_local(env, oop(new_obj));
- JVM_END
虚拟机的源码比较复杂,而且完全没有相关文献和资料,所以LZ也只能简单的添加一些注释,特别是write_region这个方法,LZ没找到实现在哪里,LZ猜测这个方法的功能是设置对象的边界的,好让GC能够正确的回收内存,但由于没找到实现,所以不敢断言。
在上面的过程中调用了Copy对象的conjoint_jlongs_atomic方法,那个就是真正的复制实例数据的方法,LZ找到了这个方法的实现,给各位看一下。
- void _Copy_conjoint_jlongs_atomic(jlong* from, jlong* to, size_t count) {
- if (from > to) {
- jlong *end = from + count;
- while (from < end)
- os::atomic_copy64(from++, to++);
- }
- else if (from < to) {
- jlong *end = from;
- from += count - 1;
- to += count - 1;
- while (from >= end)
- os::atomic_copy64(from--, to--);
- }
- }
这是一个操作内存块的方法,其中atomic_copy64这个方法是用汇编语言写的,它确保了在64位的机子下也可以正确的进行内存块的拷贝操作。它的作用很简单,就是把from指针指向的内存的值赋给to指针指向的内存,也就是一个简单的拷贝操作。知道了atomic_copy64方法的作用,上面这个方法的逻辑就非常简单了。
由此可以看出,我们可以将clone方法想象成内存块的复制操作,它的速度比一般的创建对象操作要快。
原型模式的分析就到此结束了,对于虚拟机源码的研究,LZ一直在断断续续的继续着,等设计模式系列写完以后,LZ会写一些虚拟机以及虚拟机源码的相关内容,希望各位能继续支持吧。
感谢各位的收看。