二十三:原型模式详解(clone复制方法源码)
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
定义比较简单,总结一下是通过实例指定种类,通过拷贝创建对象。
在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 correct
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 // 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 copy
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);//分配内存,写入元数据信息
}
// 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 header
new_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方法想象成内存块的复制操作,它的速度比一般的创建对象操作要快
二十三:原型模式详解(clone复制方法源码)的更多相关文章
- 【详解】ThreadPoolExecutor源码阅读(二)
系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) AQS在W ...
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- 设计模式(五)——原型模式(加Spring框架源码分析)
原型模式 1 克隆羊问题 现在有一只羊 tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和 tom 羊 属性完全相同的 10 只羊. 2 传统方式解决克隆羊问题 1) 思路分析(图 ...
- Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
- 【详解】ThreadPoolExecutor源码阅读(三)
系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) 线程数量的 ...
- 【详解】ThreadPoolExecutor源码阅读(一)
系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) 工作原理简 ...
- Android事件传递机制详解及最新源码分析——ViewGroup篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...
- SpringBoot Profile使用详解及配置源码解析
在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,Spring Boot为此提供了Profile配置来解决此问题. Profile ...
- (二十三)原型模式详解(clone方法源码的简单剖析)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 原型模式算是JAVA中最简单 ...
随机推荐
- 字体图标引入到HTML---复制用代码
第一步:在样式里面声明字体: 告诉别人我们自己定义的字体 ps: font-family的名字可以自己起 src是引入了4种字体,不用修改的 @font-face { font-family: 'ic ...
- 「SAP技术」SAP MM 事务代码ME17的用法
SAP MM 事务代码ME17的用法 1,如下采购信息记录需要被归档: PIR号码,5300007816 2, ME12打上删除标记, 3, 事务代码ME17做归档 3.1 创建archive ...
- 车联网APP,安全设施薄弱的山寨品
- HDIT 来到该公司官网,打开任意一个云平台的链接,很显眼地能看见APP的下载按钮,下载,安装,抓包,使用,完全的套路,熟门熟路是不是. 再看抓取的报文,满目的HTTP协议数据: 完全没有对APP ...
- scala 语法速查
摘自官网 variables var x = 5 Good x = 6 Variable. val x = 5 Bad x = 6 Constant. var x: Double = 5 Expl ...
- python基础-TCP协议和UDP协议
TCP协议是一种流式协议,UDP协议是一种数据包协议. TCP和UDP是OSI模型中传输层的协议.TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输. TCP和UDP区别总 ...
- PyCharm注释中出现中文运行报错的解决办法
SyntaxError: Non-UTF-8 code starting with '..... 方法一:在文件首行加上 # -*- coding:utf-8 -*- 方法二:更改编码格式 File ...
- springboot中http 的get post put delete请求
组合注解(RequestMapping的变形) @GetMapping = @RequestMapping(method = RequestMethod.GET)@PostMapping = @Req ...
- fiddler---Fiddler实现手机抓包
测试app的时候发现一些问题,我们也可以通过Fiddler进行对手机app进行抓包. 手机抓包 环境准备 1.手机一台 2.电脑上必须安装Fiddler 3.Fiddler和手机保持在同一个局域网内 ...
- Jetbrain系列编辑器设置忽略目录(node_moudles)
使用Vue 或React开发,或者nodejs开发,用Idea/Webstrom 打开项目的时候,Updating Indexes到node_moudles目录的时候 会很慢很慢很慢.... 可以设置 ...
- Python自动化运维之高级函数
本帖最后由 陈泽 于 2018-6-20 17:31 编辑 一.协程 1.1协程的概念 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.(其实并没 ...