五星-原型模式常使用于以下场景--而JAVA中的任何类只要实现了Cloneable标识接口,就可以使用clone方法来进行对象的拷贝

 作者:zuoxiaolong8810(左潇龙),转载请注明出处。

                原型模式算是Java中最简单的设计模式了,原因是因为它已经被提供了语言级的支持,但是如果提到它的实现原理,又是最复杂的一个设计模式。

                下面我们先来看看这个又简单又复杂的设计模式的定义。

                定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

                定义比较简单,总结一下是通过实例指定种类,通过拷贝创建对象。

                在JAVA语言中使用原型模式是非常简单的,这是因为Object类当中提供了一个本地方法clone,而JAVA中的任何类只要实现了Cloneable标识接口,就可以使用clone方法来进行对象的拷贝。

                我们写一个简单的实例来测试一下,很简单。

[java]  view plain  copy
  1. package com.prototype;  
  2.   
  3. public class Prototype implements Cloneable {  
  4.   
  5.     private int x;  
  6.     private int y;  
  7.     private int z;  
  8.   
  9.     public Prototype() {  
  10.         this.x = 2;  
  11.         this.y = 3;  
  12.         this.z = 4;  
  13.     }  
  14.   
  15.     public void change() {  
  16.         this.x = 9;  
  17.         this.y = 8;  
  18.         this.z = 7;  
  19.     }  
  20.   
  21.     public Prototype clone() {  
  22.         Object object = null;  
  23.         try {  
  24.             object = super.clone();  
  25.         } catch (CloneNotSupportedException exception) {  
  26.             throw new RuntimeException(exception);  
  27.         }  
  28.         return (Prototype) object;  
  29.     }  
  30.   
  31.     public String toString() {  
  32.         return "[" + x + "," + y + "," + z + "]";  
  33.     }  
  34.   
  35.     public static void main(String[] args) {  
  36.         Prototype prototype1 = new Prototype();  
  37.         prototype1.change();  
  38.         System.out.println(prototype1);  
  39.         Prototype prototype2 = prototype1.clone();  
  40.         System.out.println(prototype2);  
  41.     }  
  42.   
  43. }  
输入结果:

[9,8,7]
[9,8,7]

                 

               从输出结果可以看出来,clone方法将prototype1复制了一个,然后赋给了prototype2,这就像复制粘贴一样。值得注意的是,在使用Object.clone()方法去拷贝一个对象时,构造方法是不被执行的,否则prototype2实例中x,y,z的值应该为2,3,4才对,如果你觉得不够直观,可以在构造方法里写一个输出语句试试。

               从原型模式的使用方式不难推断出,原型模式常使用于以下场景:

               1、对象的创建非常复杂,可以使用原型模式快捷的创建对象。

               2、在运行过程中不知道对象的具体类型,可使用原型模式创建一个相同类型的对象,或者在运行过程中动态的获取到一个对象的状态。

               

               对于clone方法,它执行的是浅拷贝,也就是说如果是引用类型的属性,则它不会进行拷贝,而是只拷贝引用。

               看下面这个简单的测试,就能看出来了。

[java]  view plain  copy
  1. package com.prototype;  
  2.   
  3. class Field{  
  4.       
  5.     private int a;  
  6.   
  7.     public int getA() {  
  8.         return a;  
  9.     }  
  10.   
  11.     public void setA(int a) {  
  12.         this.a = a;  
  13.     }  
  14.       
  15. }  
  16.   
  17. public class ShallowPrototype implements Cloneable {  
  18.   
  19.     private int x;  
  20.     private int y;  
  21.     private int z;  
  22.     private Field field;  
  23.   
  24.     public ShallowPrototype() {  
  25.         this.x = 2;  
  26.         this.y = 3;  
  27.         this.z = 4;  
  28.         this.field = new Field();  
  29.         this.field.setA(5);  
  30.     }  
  31.       
  32.     public Field getField() {  
  33.         return field;  
  34.     }  
  35.   
  36.     public ShallowPrototype clone() {  
  37.         Object object = null;  
  38.         try {  
  39.             object = super.clone();  
  40.         } catch (CloneNotSupportedException exception) {  
  41.             throw new RuntimeException(exception);  
  42.         }  
  43.         return (ShallowPrototype) object;  
  44.     }  
  45.   
  46.     public String toString() {  
  47.         return "[" + x + "," + y + "," + z + "," + field.getA() + "]";  
  48.     }  
  49.   
  50.     public static void main(String[] args) {  
  51.         ShallowPrototype prototype1 = new ShallowPrototype();  
  52.         System.out.println(prototype1);  
  53.         System.out.println(prototype1.getField());  
  54.         ShallowPrototype prototype2 = prototype1.clone();  
  55.         System.out.println(prototype2);  
  56.         System.out.println(prototype2.getField());  
  57.     }  
  58.   
  59. }  
输入结果:

[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方法,而且需要将引用类型的属性全部拷贝一遍。

               下面是一个简单的深度拷贝的例子,由上面的例子更改得到。

[java]  view plain  copy
  1. package com.prototype;  
  2.   
  3. class Field implements Cloneable{  
  4.       
  5.     private int a;  
  6.   
  7.     public int getA() {  
  8.         return a;  
  9.     }  
  10.   
  11.     public void setA(int a) {  
  12.         this.a = a;  
  13.     }  
  14.       
  15.     protected Field clone() {  
  16.         Object object = null;  
  17.         try {  
  18.             object = super.clone();  
  19.         } catch (CloneNotSupportedException exception) {  
  20.             throw new RuntimeException(exception);  
  21.         }  
  22.         return (Field) object;  
  23.     }  
  24.       
  25. }  
  26.   
  27. public class DeepPrototype implements Cloneable {  
  28.   
  29.     private int x;  
  30.     private int y;  
  31.     private int z;  
  32.     private Field field;  
  33.   
  34.     public DeepPrototype() {  
  35.         this.x = 2;  
  36.         this.y = 3;  
  37.         this.z = 4;  
  38.         this.field = new Field();  
  39.         this.field.setA(5);  
  40.     }  
  41.       
  42.     public Field getField() {  
  43.         return field;  
  44.     }  
  45.   
  46.     protected DeepPrototype clone() {  
  47.         Object object = null;  
  48.         try {  
  49.             object = super.clone();  
  50.             ((DeepPrototype)object).field = this.field.clone();  
  51.         } catch (CloneNotSupportedException exception) {  
  52.             throw new RuntimeException(exception);  
  53.         }  
  54.         return (DeepPrototype) object;  
  55.     }  
  56.   
  57.     public String toString() {  
  58.         return "[" + x + "," + y + "," + z + "," + field.getA() + "]";  
  59.     }  
  60.   
  61.     public static void main(String[] args) {  
  62.         DeepPrototype prototype1 = new DeepPrototype();  
  63.         System.out.println(prototype1);  
  64.         System.out.println(prototype1.getField());  
  65.         DeepPrototype prototype2 = prototype1.clone();  
  66.         System.out.println(prototype2);  
  67.         System.out.println(prototype2.getField());  
  68.     }  
  69.   
  70. }  
输出结果:

[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()的源代码,如下。

[cpp]  view plain  copy
  1. JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))  
  2.   JVMWrapper("JVM_Clone");  
  3.   Handle obj(THREAD, JNIHandles::resolve_non_null(handle));  
  4.   const KlassHandle klass (THREAD, obj->klass());  
  5.   JvmtiVMObjectAllocEventCollector oam;  
  6.   
  7. #ifdef ASSERT  
  8.   // Just checking that the cloneable flag is set correct  
  9.   if (obj->is_javaArray()) {  
  10.     guarantee(klass->is_cloneable(), "all arrays are cloneable");  
  11.   } else {  
  12.     guarantee(obj->is_instance(), "should be instanceOop");  
  13.     bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());  
  14.     guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");  
  15.   }  
  16. #endif  
  17.   
  18.   // Check if class of obj supports the Cloneable interface.  
  19.   // All arrays are considered to be cloneable (See JLS 20.1.5)  
  20.   if (!klass->is_cloneable()) {//这里检查了是否实现了Cloneable接口,如果没实现,会抛出异常CloneNotSupportException。  
  21.     ResourceMark rm(THREAD);  
  22.     THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());  
  23.   }  
  24.   
  25.   // Make shallow object copy  
  26.   const int size = obj->size();//取对象大小  
  27.   oop new_obj = NULL;  
  28.   if (obj->is_javaArray()) {//如果是数组  
  29.     const int length = ((arrayOop)obj())->length();//取长度  
  30.     new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);//分配内存,写入元数据信息  
  31.   } else {  
  32.     new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);//分配内存,写入元数据信息  
  33.   }  
  34.   // 4839641 (4840070): We must do an oop-atomic copy, because if another thread  
  35.   // is modifying a reference field in the clonee, a non-oop-atomic copy might  
  36.   // be suspended in the middle of copying the pointer and end up with parts  
  37.   // of two different pointers in the field.  Subsequent dereferences will crash.  
  38.   // 4846409: an oop-copy of objects with long or double fields or arrays of same  
  39.   // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead  
  40.   // of oops.  We know objects are aligned on a minimum of an jlong boundary.  
  41.   // The same is true of StubRoutines::object_copy and the various oop_copy  
  42.   // variants, and of the code generated by the inline_native_clone intrinsic.  
  43.   assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");  
  44.   Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,  
  45.                                (size_t)align_object_size(size) / HeapWordsPerLong);//这一步就是真正的COPY内存块了  
  46.   // Clear the header  
  47.   new_obj->init_mark();//初始化对象头,里面包含了Hashcode,GC信息,锁信息等,因为拷贝出的对象是一个全新的对象,所以这些信息需要初始化一下。  
  48.   
  49.   // Store check (mark entire object and let gc sort it out)  
  50.   BarrierSet* bs = Universe::heap()->barrier_set();  
  51.   assert(bs->has_write_region_opt(), "Barrier set does not have write_region");  
  52.   bs->write_region(MemRegion((HeapWord*)new_obj, size));//write_region最终的实现在一个虚方法里,相当于JAVA的抽象方法,LZ没找到实现。暂不发表意见。  
  53.   
  54.   // Caution: this involves a java upcall, so the clone should be  
  55.   // "gc-robust" by this stage.  
  56.   if (klass->has_finalizer()) {//如果有finalize方法,则需要注册一下。  
  57.     assert(obj->is_instance(), "should be instanceOop");  
  58.     new_obj = instanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);  
  59.   }  
  60.   
  61.   return JNIHandles::make_local(env, oop(new_obj));//将内存对象转换成JAVA本地对象返回  
  62. JVM_END  
               虚拟机的源码比较复杂,而且完全没有相关文献和资料,所以LZ也只能简单的添加一些注释,特别是write_region这个方法,LZ没找到实现在哪里,LZ猜测这个方法的功能是设置对象的边界的,好让GC能够正确的回收内存,但由于没找到实现,所以不敢断言。

               在上面的过程中调用了Copy对象的conjoint_jlongs_atomic方法,那个就是真正的复制实例数据的方法,LZ找到了这个方法的实现,给各位看一下。

[cpp]  view plain  copy
  1. void _Copy_conjoint_jlongs_atomic(jlong* from, jlong* to, size_t count) {  
  2.   if (from > to) {  
  3.     jlong *end = from + count;  
  4.     while (from < end)  
  5.       os::atomic_copy64(from++, to++);  
  6.   }  
  7.   else if (from < to) {  
  8.     jlong *end = from;  
  9.     from += count - 1;  
  10.     to   += count - 1;  
  11.     while (from >= end)  
  12.       os::atomic_copy64(from--, to--);  
  13.   }  
  14. }  
              这是一个操作内存块的方法,其中atomic_copy64这个方法是用汇编语言写的,它确保了在64位的机子下也可以正确的进行内存块的拷贝操作。它的作用很简单,就是把from指针指向的内存的值赋给to指针指向的内存,也就是一个简单的拷贝操作。知道了atomic_copy64方法的作用,上面这个方法的逻辑就非常简单了。

              由此可以看出,我们可以将clone方法想象成内存块的复制操作,它的速度比一般的创建对象操作要快

              

              原型模式的分析就到此结束了,对于虚拟机源码的研究,LZ一直在断断续续的继续着,等设计模式系列写完以后,LZ会写一些虚拟机以及虚拟机源码的相关内容,希望各位能继续支持吧。

              感谢各位的收看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值