前言

这篇讲设计模式的部分相对较少。Prototype设计模式,它提供一种复制对象的思路。使用Prototype就可以在不需要了解类结构的前提下,复制一个现有对象。写了一个代码片段,讲解使用Object.clone()要注意浅拷贝,深拷贝的问题。最后,去找到clone实现的native代码,大致了解一下复制的过程,知道了底层实现是浅拷贝

Java中的clone()

Java中,有一个Cloneable接口。如果去查看它的代码,会发现这个接口里面什么都没有。这种什么都没有的接口被称之为Marker Interface,实现这个接口的类,使用instanceof关键字可以检查它是否为Cloneable。真正的clone函数在Object.java中,当调用Object的clone方法的时候,它会去检查是否显式地指定实现Cloneable接口,否则会抛出异常。

public interface Cloneable {
}

// Object.java
protected native Object clone() throws CloneNotSupportedException;

在Android中clone的实现,先用java代码去检查是否显式指定实现了Cloneable接口,然后调用native代码。

protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                " doesn't implement Cloneable");
    }

    return internalClone();
}

/*
* Native helper method for cloning.
*/
@FastNative
private native Object internalClone();

探索clone()

下面举个使用clone的例子,这里涉及所谓的深拷贝,浅拷贝的问题。浅拷贝,拷贝的仅仅是对象的地址;深拷贝则会新建一个对象,将对象的成员复制到新建的对象里。

为了精简代码,这里去掉了getter和setter,以及一个show方法,show方法做的事情仅仅是打印出成员。

class Desk implements Cloneable {
    private int dollar;

    public Desk(int dollar) {
        this.dollar = dollar;
    }

    @Override
    public Desk clone() throws CloneNotSupportedException {
        return (Desk) super.clone();
    }
}

class House implements Cloneable {
    private Desk desk;
    private int rooms;

    public House(int rooms) {
        this.rooms = rooms;
    }

    @Override
    public House clone() throws CloneNotSupportedException {
        return (House) super.clone();
    }
}

浅拷贝

注意到,以上代码,直接调用super.clone()来复制House。在main函数中,调用以下代码,观察打印出来的信息。

main函数的代码:

  1. 初始化房子A和桌子X
  2. 以房子A为模板,复制房子B
  3. 获取B的桌子Y
  4. 设置Y的价格

经过以上步骤,打印信息显示A房子的桌子X价格也更改了。这说明房子B的桌子Y,这个对象的地址指向了房子A的桌子X的地址。XY是同一个对象,使用同一个地址。这说明调用super.clone()的时候,类的成员是通过复制出来的。

// 打印的信息:
This House has 5 rooms, The Desk is $2333
This House has 5 rooms, The Desk is $2333
This House has 5 rooms, The Desk is $1111
This House has 5 rooms, The Desk is $1111

// Initialize
House house = new House(5);
Desk desk = new Desk(2333);
house.setDesk(desk);

// Clone
House cloneHouse = null;
try {
    cloneHouse = house.clone();
}
catch (CloneNotSupportedException e) {
    e.printStackTrace();
    return;
}

// Show
house.show();
cloneHouse.show();

// setDesk
Desk deskOfCloneHouse = cloneHouse.getDesk();
deskOfCloneHouse.setDollar(1111);

// Show again
house.show();
cloneHouse.show();

深拷贝

将House下面的clone改为以下代码,新House下的桌子,不再和原House下的桌子相同。

@Override
public House clone() throws CloneNotSupportedException {
    House house = (House) super.clone();
    house.setDesk(desk.clone());
    return house;
}

clone的实现

Object下面的clone,调用本地(native)代码来实现对象的clone,那么它是如何实现的呢?让我们来把这个黑箱打开吧!

protected native Object clone() throws CloneNotSupportedException;

通过链接[3],可以找到clone()实现的代码片段。这里使用[2]看到的片段,第539行开始。这里为了节省篇幅,用"..."去掉部分代码。

这段代码的工作流程大致为:

  1. 检查这个类是否显式指定了实现Cloneable接口。如果没有,那么抛出异常
  2. 注释可以看到"Make shallow object copy",进行浅拷贝。
  3. 在栈中分配要复制对象的空间大小
  4. 进行内容的复制。去掉的注释部分,讲的大概是要保证复制的线程安全。使用atomic操作,因为在复制内容的过程中,可能有另一个线程在操作被复制对象的成员。
  5. make_local,将这个新建的对象加入到运行环境中。

关于finalize的实现,还不甚了解。这里纯属猜想:如果一个类声明了finalize方法,那么在会给他注册绑定一个finalizer。clone一个对象,如果被复制对象的类有finalize方法,那么新对象要注册一个finalizer。

JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
  ...
  // 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()) {
    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);
  }
  ...
  Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
                               (size_t)align_object_size(size) / HeapWordsPerLong);
  ...
  // Caution: this involves a java upcall, so the clone should be
  // "gc-robust" by this stage.
  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

参考链接

  1. https://www.cnblogs.com/Qian123/p/5710533.html
  2. http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/prims/jvm.cpp
  3. https://stackoverflow.com/questions/12032292/is-it-possible-to-find-the-source-for-a-java-native-method
  4. https://refactoring.guru/design-patterns/prototype

【设计模式】Prototype的更多相关文章

  1. 设计模式:Prototype 原型模式 - 同学你抄过别人的作业么?-clone()方法的使用

    原型模式: 通过某个类的实例来创建对象 使用原型模式的好处: 好处是什么呢?当我们需要多次重复的创建一个类的示例的时候,我们可以使用new但是,new不仅仅耗费内存而且,如果new 某个类的构造方法中 ...

  2. 设计模式-Prototype(通过复制构造函数实现自我复制)-(创建型模式)

    以下代码来源: 设计模式精解-GoF 23种设计模式解析附C++实现源码 //Prototype.h #pragma once class Prototype { public: virtual ~P ...

  3. C++设计模式-Prototype原型模式

    作用: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. Prototype模式提供了一个通过已存在对象进行新对象创建的接口(Clone), Clone()实现和具体的语言相关,在C+ ...

  4. 5.设计模式----prototype原型模式

    原型模式:做到是原型,那肯定是自己本身才是原型,原型模式属于对象的创建模式. 关于原型模式的实现方式分2种: (1)简单形式.(2)登记形式,这两种表现形式仅仅是原型模式的不同实现. package ...

  5. 23种设计模式——Prototype模式

    Prototype模式是提供自我复制的功能.包括浅拷贝和深拷贝. 一.Prototype模式的用途 场景1:游戏场景中有很多类似的敌人,它们的技能都一样,但是随着敌人出现的位置和不同,它们的能力也不太 ...

  6. 一天一个设计模式——Prototype 原型模式

    一.模式说明 看了比较多的资料,对原型模式写的比较复杂,个人的理解就是模型复制,根据现有的类来直接创建新的类,而不是调用类的构造函数. 那为什么不直接调用new方法来创建类的实例呢,主要一个原因是如果 ...

  7. 设计模式------PROTOTYPE(原型),TEMPLATE(模板)

    看链接:http://blog.csdn.net/wuzhekai1985/article/details/6667020.纯属为自己学习所使用. 对于原型模式的理解:就如连接中所说,制作简历时先手写 ...

  8. 原型设计模式 Prototype

    参考1 http://www.cnblogs.com/libingql/p/3633377.html http://www.cnblogs.com/promise-7/archive/2012/06/ ...

  9. Java 设计模式实现 不错的引用

    这段时间有兴趣重新温习一下设计模式在Java中的实现,碰巧看到一个不错的设计模式总结,这里引用一下作为参考. 创建型模式: JAVA设计模式-Singleton JAVA设计模式-Factory JA ...

  10. [php]php设计模式 (总结)

    转载自[php]php设计模式 (总结) 传统的23种模式(没有区分简单工厂与抽象工厂) http://www.cnblogs.com/bluefrog/archive/2011/01/04/1925 ...

随机推荐

  1. JVM运行机制(非原创)

    文章大纲 JVM基本概念 JVM的体系结构 JVM启动流程 一.JVM基本概念 Java虚拟机(JVM)是可运行Java代码的假想计算机 Java虚拟机包括类加载器.一组寄存器.方法区.一个垃圾回收堆 ...

  2. 关于如何刷新清除某个特定网站的缓存---基于Chrome浏览器

    今天工作时又发现了一个小技巧 1.打开一个网站如:百度 2.打开F12开发者模式 3.右键浏览器的刷新按钮,会出现三个选项:正常重新加载,硬性重新加载,清空缓存并硬性重新加载 正常重新加载 Ctrl+ ...

  3. Eclipse修改JSP文件的默认编码

    Eclipse新建JSP文件,可以看到默认使用的是ISO-8859-1编码,如下图,而这种编码是无法保存中文的,不符合我们的需求 那么应该怎样修改呢?找到菜单Window-Preferences,找到 ...

  4. MySQL MGR 集群从数据库显示RECOVRING

    因为断电 或者 其他瞎折腾 导致: 从节点显示RECOVRING 查看错误日志显示: Slave SQL for channel 'group_replication_recovery': Error ...

  5. 【转】gdb的调试与使用

    转载自:https://www.jianshu.com/p/7a06b0bda2d8 gdb的调试与使用 这篇应该是我见过的总结最详细的gdb调试指南了,这位博主是个很强的人,他的博客对萌新比较友好, ...

  6. zabbix使用钉钉告警

    1.钉钉创建群 2.[root@localhost ~]# vim /etc/zabbix/zabbix_server.conf # 配置文件中查找”Alert”查看告警脚本存放路径 [root@lo ...

  7. CF798D Mike and distribution

    CF798D Mike and distribution 洛谷评测传送门 题目描述 Mike has always been thinking about the harshness of socia ...

  8. split task

    和印度的team合作的时候,他们经常有些要求,然后我们这边有时候需要改代码来满足他们的需求. 最近一次,他们要求在我们的一个工具中为他们加入2个asp.net的注册命令,不知道什么原因,system ...

  9. CF-1132 C.Painting the Fence

    题目大意:现在有n个栅栏板,p个工人,每个工人可以涂一段区间的栅栏板,问如果雇佣p-2个工人,最多可以涂多少块栅栏板. 做法:先求出q个工人能涂得最多木板数,并统计每个木板被涂的次数.求被涂一次的木板 ...

  10. Codeforces Round #552 (Div. 3) EFG(链表+set,dp,枚举公因数)

    E https://codeforces.com/contest/1154/problem/E 题意 一个大小为n(1e6)的数组\(a[i]\)(n),两个人轮流选数,先找到当前数组中最大的数然后选 ...