简书: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异常
下面我们来看看如果对象中的域引用了可变的对象,使用上述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