Java中Object.clone()方法详细解析

简书:capo
转载请注明原创出处,谢谢!

前言:

今天,我们来聊聊Object中clone()方法实现细节。首先我们看一下JDK8源码中关于clone()方法设计的规范

/**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     创建一个复制对象返回,这个明确的辅助获取依靠对象的 class属性
     
     通常情况下,对于任何对象x这个表达式
     x.clone() != x
     而且表达式 
     x.clone().getClass() == x.getClass()将是true,但这些都不是绝对的要求
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     当这个代表性的情况
     x.equals(x)将会是true,但这不是绝对要求的
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     按照惯例,这个返回的对象应该通过调用 super.clone 获得,如果一个类和它的父可遵守这个惯例,那将是 x.clone().getClass() == x.getClass()
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     按照惯例,这个方法返回的对象应该与正在被克隆的西乡没有依赖关系
 
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  为了达到这个独立性,它将有必要去修改super.clone返回的对象的一个或多个字段。
     通常,这意味着复制构成被克隆的对象的内部深层结构的任何可变对象,并通过引用该副本替换对这些对象的引用,如果一个类仅包含原始字段或不可变对象的引用,则通常情况下,super.clone返回的对象中的字段通常不需要修改
     To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     
     
     clone方法Object执行特性的克隆操作,首先,如果此对象的类不实现Clonable,则抛出CloneNotSupportedException。请注意,所有的数组都被认为是实现了Clonable
     请注意所有数组都被认为是实现了 Cloneable,并且数组类型 T[]的clone方法返回类型是 T,其真T是任何引用作用域原始类型. 否则,该方法将创建该对象的类的新实例,并将所有初始化为完全符合该对象的相应字段的内容,就像通过复制一样。这些字段的内容本身不被克隆. 因此.该方法执行该对象的 浅拷贝 ,而不是 深度拷贝
     
     Object类本身并不实现clonable接口,因此在类object的对象上调用clone方法将导致运行时异常
     
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
 protected native Object clone() throws CloneNotSupportedException;

看了上述规范,我们可能会想,如果我们扩展一个类,并且在子类中调用了super.clone()(调用父类的clone),返回的对象就是该子类的实例。超类能够提供这种功能的唯一一个途径是,返回一个通过调用super.clone而得到的对象,在通过强制声明就可以转换为子类的实例了。

例如: 下面一段代码:

@override public PhoneNumber clone() {
          try{
              return (PhoneNuumber) super.clone();
              }catch(CloneNotSupportedException e) {
                 //      
}
         
}

上述代码 super.clone实际是 调用Object.clone()返回Object,然后在进行强制转换就可以了


Object.clone()方法是一个对对象进行浅拷贝的方法,但是调用这个方法的类需要依赖去实现Cloneable接口,否则会抛出CloneNotSupportedException异常

image.png

下面我们来看看如果对象中的域引用了可变的对象,使用上述clone实现可能会导致灾难性的后果

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

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

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

        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; // Eliminate obsolete reference
            return result;
        }

        // Ensure space for at least one more element.
        private void ensureCapacity() {
            if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
}

如果你把这个类做成克隆类(让这个类实现cloneable接口)。如果它的clone方法仅仅返回 super.clone(),这样得到的Stack实例,在其size域中是一个固定的值,但是它的elements域将引用与原始Stack实例相同的数组。如果你修改原始的实例则会破坏掉被克隆对象中的约束条件,如果你修改克隆对象的elements域则会影响原始对象的域。所以这个程序很可能会抛出 NullPointException异常。

为了使Stack类中的clone方法能正常地工作,它必须拷贝栈的内部信息,最容易的做法是,在elements数组中递归地调用clone:

@Override public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
这里我们还有注意,如果elements域是final的,那么上述对数组的拷贝是不能正常工作的,因为clone方法是禁止给elements域赋新值的。**clone架构域引用可变对象的final域的正常用法是不相兼容的**

总结:

  • 对于任何一个对象调用clone方法可能会有一下几种情况 x.clone != x 将会返回true,x.clone().getClass() == x.getClass()将会返回true,x.clone().equals(x)将会返回true这些都不是一个绝对的要求。拷贝对象往往会导致创建一个新的实例,但它同时也会要求拷贝内部的数据结构。这个过程是没有调用构造器的
  • Object.clone()方法是一个对对象进行浅拷贝的方法,但是调用这个方法的类需要依赖去实现Cloneable接口,否则会抛出CloneNotSupportedException异常
  • 如果你要正确的拷贝一个对象,首先你要将这个对象实现cloneable接口,然后应该重写 Objec的clone方法,此方法里面首先应该调用 super.clone,然后修正任何需要修改的域
  • clone方法方法的设计是不允许重新赋值可变对象final域的
  • clone方法就是一个构造器:你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件
  • 如果你决定用线程安全的类实现 Cloneable接口,要记得它的clone方法必须得到很好的同步(比如方法加锁)。

总之,如果你要对一个对象进行拷贝,请实现Cloneable接口,并谨慎的重写clone方法,保证原始对象中实例域域克隆对象实例域互不影响

参考文章 Effective Java

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

推荐阅读更多精彩内容