作者: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// Just checking that the cloneable flag is set correctif (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// Check if class of obj supports the Cloneable interface.// All arrays are considered to be cloneable (See JLS 20.1.5)if (!klass->is_cloneable()) {//这里检查了是否实现了Cloneable接口,如果没实现,会抛出异常CloneNotSupportException。
    ResourceMark rm(THREAD);THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());}// Make shallow object copyconst 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);//分配内存,写入元数据信息
  }// 4839641 (4840070): We must do an oop-atomic copy, because if another thread// is modifying a reference field in the clonee, a non-oop-atomic copy might// be suspended in the middle of copying the pointer and end up with parts// of two different pointers in the field.  Subsequent dereferences will crash.// 4846409: an oop-copy of objects with long or double fields or arrays of same// won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead// of oops.  We know objects are aligned on a minimum of an jlong boundary.// The same is true of StubRoutines::object_copy and the various oop_copy// variants, and of the code generated by the inline_native_clone intrinsic.assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,(size_t)align_object_size(size) / HeapWordsPerLong);//这一步就是真正的COPY内存块了// Clear the headernew_obj->init_mark();//初始化对象头,里面包含了Hashcode,GC信息,锁信息等,因为拷贝出的对象是一个全新的对象,所以这些信息需要初始化一下。// Store check (mark entire object and let gc sort it out)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));//write_region最终的实现在一个虚方法里,相当于JAVA的抽象方法,LZ没找到实现。暂不发表意见。// Caution: this involves a java upcall, so the clone should be// "gc-robust" by this stage.if (klass->has_finalizer()) {//如果有finalize方法,则需要注册一下。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));//将内存对象转换成JAVA本地对象返回
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会写一些虚拟机以及虚拟机源码的相关内容,希望各位能继续支持吧。

感谢各位的收看。

(二十三)原型模式详解(clone方法源码的简单剖析)相关推荐

  1. 设计模式-值类型与引用类型、深拷贝与浅拷贝、原型模式详解

    一. 值类型和引用类型 1. 前言 (1). 分类 值类型包括:布尔类型.浮点类型(float.double.decimal.byte).字符类型(char).整型(int.long.short等). ...

  2. IoC与DI工厂、单例、原型模式详解

    1.工厂模式 1.1 工厂模式的由来 在现实生活中我们都知道 原始社会自给自足(没有工厂) 农耕社会有了小作坊(简单工厂,如民间酒坊) 工业革命后有了流水线(工厂方法,自产自销) 现代产业链中有代工厂 ...

  3. 设计模式之原型模式详解(附应用举例实现)

    文章目录 1 原型模式介绍 2 原型模式详解 2.1 原型模式结构 2.2 深克隆与浅克隆 2.2.1 浅克隆 2.2.2 深克隆 2.3 原型模式实现 2.3.1 通用实现方法 2.3.2 Java ...

  4. 设计模式(四)——原型模式详解

    设计模式(四)--原型模式详解 定义 结构 实现 案例 浅克隆 深克隆 定义 原型模式就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的对象. 结构 原型模式包含以下角色: ...

  5. (十二)命令模式详解(故事版)- 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 背景:小左是魔都某公司技术部的一名屌丝程序猿,每天的工作就是维护一个20世纪的古董级项目,由于公司不大,所以公司很多制度不太完善,导致 ...

  6. (二十二)访问者模式详解(伪动态双分派) - 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 本次LZ和各位分享一下访问者模式,从场景.设计初衷以及实现方面来说,访问者模式算是LZ即将写到的24种设计模式当中,最复杂也是最难理解 ...

  7. aes js加密php解密实例,基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_PHP_JS_AES源码...

    [实例简介] 基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_PHP_JS_AES源码 [实例截图] [核心代码] 基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_ ...

  8. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  9. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

最新文章

  1. 用python快速画小猪佩奇
  2. String、StringBuffer、StringBuilder的理解
  3. CISCO 3550交换机配置DHCP三步骤
  4. Vista下控件无法安装解决办法
  5. Part2_1 Urllib的get请求和post请求
  6. 【Python】print 不换行输出
  7. P4555-[国家集训队]最长双回文串【Manacher】
  8. 【ZOJ - 3715】Kindergarten Election(枚举得票数,贪心)
  9. mariadb使用mysql驱动_MariaDB安装与使用
  10. Oracle 练习P297 131026 PL/SQL块程序
  11. 基于mpi的奇偶排序_基于MPI的PSRS并行排序算法的实现
  12. mysql语法错误文件_使用logstash同步MySQL的数据时,在jdbc查询sql文件时报sql语法错误,sql文件是navicat生成的...
  13. 如何设置GridView的列宽
  14. linux环境搭建之tftp tftpd服务器
  15. C语言学习复盘整理笔记(六)
  16. java汉字转拼音maven_java汉字转拼音pinyin4j功能实现示例
  17. python游戏挂机脚本_python游戏挂机
  18. win7局域网计算机无法访问,win7局域网不能访问怎么办_win7系统电脑无法访问局域网怎么办-win7之家...
  19. SAI颈部正面的画法
  20. 现场直播:域名转出的黑幕和愤怒!(商务中国BIZCN和美橙互联CNDNS)

热门文章

  1. chrome出现adobe flash playe 不是最新版本
  2. Joiner的简单了解
  3. 飘逸的python - property及实现lazy property
  4. 关于 AppDelegate 、UIApplication 简单的用法
  5. 2014年下半年信息系统项目管理师上午试题试题与答案 54
  6. SQL Server索引设计 第五篇
  7. Extreme 交换机基础配置命令
  8. Web安全实践(9)攻击apache
  9. 一个学机械的毕业生令中国人无法安眠的帖子
  10. Dell 2850服务器磁盘阵列两块硬盘离线恢复过程