java中clone方法的实现

java中仅有的创建对象的两种方式:①.使用new操作符创建对象;②.使用clone方法复制对象。由于clone方法将最终将调用JVM中的原生方法完成复制,所以一般使用clone方法复制对象要比新建一个对象然后逐一进行元素复制效率要高。

浅拷贝与深拷贝

在java中基本数据类型是按值传递的,而对象是按引用传递的。所以当调用对象的clone方法进行对象复制时将涉及深拷贝和浅拷贝的概念。

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。通过clone方法复制对象时,若不对clone()方法进行改写,则调用此方法得到的对象为浅拷贝。

例如:浅拷贝


    public class Stack implements Cloneable {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;

        public Stack() {
            elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }

        public void push(Object o) {
            ensureCapacity();
            elements[size++] = o;
        }

        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; // 【避免内存泄漏】
            return result;
        }

        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }

        // 实现clone方法,浅拷贝
        @Override
        protected Stack clone() throws CloneNotSupportedException {

            return (Stack) super.clone();
        }
    }

深拷贝:

    
    //深拷贝
    @Override
    protected Stack clone() throws CloneNotSupportedException {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone(); //对elements元素进行拷贝(引用或基本数据类型)
        return result;
    }

其原理图:


深拷贝与浅拷贝的原理

注意:

  • 由于java5.0后引入了协变返回类型(covariant return type)实现(基于泛型),即覆盖方法的返回类型可以是被覆盖方法的返回类型的子类型,所以clone方法可以直接返回Stack类型,而不用返回Object类型,然后客户端再强转。
  • 在数组上调用clone返回的数组,其编译时类型与被克隆数组的类型相同。
  • 若elements域是final的,深拷贝不能正常工作。因为clone架构与引用可变对象的final域的正常用法是不兼容的。
  • 若elements数组中的元素是引用类型,则此方法仅仅是对引用的拷贝,元素指向的还是原来的对象

还应该注意,数组的clone,仅仅复制的是数组中的元素,即若数组中元素为引用类型,仅仅复制引用。若clone的对象中含有链表,则应单独对链表进行循环复制。例如,一个内部包含一个散列桶数组的散列表,其数组中每个元素都指向一个独立的链表。此时仅仅使用上面的方法就是不完全拷贝。

代码:


    public class HashTable implements Cloneable {

        private static final int CAPACITY = 10;

        //散列桶数组,数组中元素指向由Entry对象组成的链表(指向链表第一个Entry)
        private Entry[] buckerts = new Entry[CAPACITY];

        public void put(Object key, Object value) {
            int index = key.hashCode() % CAPACITY;
            Entry e = buckerts[index];
            buckerts[index] = new Entry(key,value,e);
        }

        @Override
        public HashTable clone() throws CloneNotSupportedException {
            HashTable result = (HashTable)super.clone();
            result.buckerts = buckerts.clone(); //仅仅复制了对链表的引用。
            return result;
        }

        //轻量级单链表
        private static class Entry {
            final Object key;
            Object value;
            Entry next;

            Entry(Object key, Object value, Entry next) {
                this.key = key;
                this.value = value;
                this.next = next;
            }
        }
    }

原理图:


不完全拷贝

虽然被克隆对象有自己的散列桶数组,但数组引用的链表与原对象是一样的。数组的clone方法,仅仅拷贝了对链表的引用,而没有复制链表中的元素。

改进代码:


    @Override
    public HashTable clone() throws CloneNotSupportedException {
        HashTable result = (HashTable)super.clone();
        result.buckerts = buckerts.clone();
        for(int i=0; i<buckerts.length; i++) {
            result.buckerts[i] = buckerts[i].deepCopy();
        }
        return result;
    }

    //轻量级单链表
    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        //递归实现链表复制
        Entry deepCopy() {
            return new Entry(key,value,next == null ? null : next.deepCopy());
        }
    }

在内部类Entry中的深度拷贝方法递归的调用自身,以完成链表的拷贝。虽然这种方法比较简洁,但如果链表很长,有可能会导致栈溢出。可以使用迭代代替递归实现链表的复制。代码如下:


    //迭代实现链表复制
    Entry deepCopy() {
        Entry result = new Entry(key, value, next);
        for(Entry e = result; e.next != null; e = e.next) {
            e.next = new Entry(e.next.key, e.next.value, e.next.next);
        }
        return result;
    }

实现clone方法的步骤:

  • 首先调用父类的super.clone方法(父类必须实现clone方法),这个方法将最终调用Object的中native型的clone方法完成浅拷贝
  • 对类中的引用类型进行单独拷贝
  • 检查clone中是否有不完全拷贝(例如,链表),进行额外的复制

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,493评论 0 3
  • 相关概念 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对...
    东经315度阅读 1,936评论 0 8
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,605评论 18 399
  • 前几天,有朋友去面试之前问我关于后端架构相关的问题,但奈于我去年更多的工作是在移动SDK开发上,对此有所遗忘,实属...
    涅槃1992阅读 5,209评论 8 76
  • 说实话,这次是我长这么大以来,第一次和爸爸看的电影。看的《天将雄狮》,爸爸自从我出生后就很少去过电影院了,难...
    WavePEACH阅读 1,113评论 10 18