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:
     * 
*
     * x.clone() != x
* will be true, and that the expression: *
*
     * x.clone().getClass() == x.getClass()
* will be {@code true}, but these are not absolute requirements. 当这个代表性的情况 x.equals(x)将会是true,但这不是绝对要求的 * While it is typically the case that: *
*
     * x.clone().equals(x)
* will be {@code true}, this is not an absolute requirement. *

按照惯例,这个返回的对象应该通过调用 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()}. *

按照惯例,这个方法返回的对象应该与正在被克隆的西乡没有依赖关系 * 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. *

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. *

* 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异常

Java中Object.clone()方法详细解析_第1张图片
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

你可能感兴趣的:(Java中Object.clone()方法详细解析)