• 【设计模式】Prototype


    前言

    这篇讲设计模式的部分相对较少。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
  • 相关阅读:
    Typora使用腾讯云图床
    2020年8月总结
    113 路径之和II
    103 二叉树的锯齿形层次遍历
    128 最长连续序列
    160 相交链表
    33 搜索旋转排序数组
    学习制作GitHub徽标
    105 从前序与中序遍历序列构造二叉树
    重新封装了layer.tips,自定义跟随弹窗
  • 原文地址:https://www.cnblogs.com/zzk0/p/11227670.html
Copyright © 2020-2023  润新知