Java中怎么拷贝一个对象呢?可以通过调用这个对象类型的构造器构造一个新对象,然后将要拷贝对象的属性设置到新对象里面。Java中也有另一种不通过构造器来拷贝对象的方式,这种方式称为
克隆。
Java提供了java.lang.Cloneable和java.lang.Object中的clone()方法来支持克隆。Cloneable接口表示一种可以克隆的行为或特性,但这个接口中却没有提供clone方法(Cloneable接口中没有任何方法,看起来更像是一个标记接口)。
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @version %I%, %G%
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
相反在Object中却存在一个clone方法,这一点很奇怪。
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object <tt>x</tt>, 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 <tt>true</tt>, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be <tt>true</tt>, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* <tt>super.clone</tt>. If a class and all of its superclasses (except
* <tt>Object</tt>) obey this convention, it will be the case that
* <tt>x.clone().getClass() == x.getClass()</tt>.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by <tt>super.clone</tt> 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 <tt>super.clone</tt>
* need to be modified.
* <p>
* The method <tt>clone</tt> for class <tt>Object</tt> performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface <tt>Cloneable</tt>, then a
* <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays
* are considered to implement the interface <tt>Cloneable</tt>.
* 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 <tt>Object</tt> does not itself implement the interface
* <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object
* whose class is <tt>Object</tt> will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @exception CloneNotSupportedException if the object's class does not
* support the <code>Cloneable</code> interface. Subclasses
* that override the <code>clone</code> method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
Object中的clone方法为受保护方法。既然所有的对象都存在一个clone方法,那是不是都可以调用(可通过反射)这个方法来得到克隆对象呢?clone方法有如下规定:
如果一个对象没有实现Cloneable接口,调用它的clone方法会抛出CloneNotSupportedException异常。但数组例外,可以认为数组实现了Cloneable,所以可以直接调用其clone方法获取克隆对象。
我们开发中应该怎样使用克隆呢?一般认为,如果希望一个类拥有克隆的行为,要做一下几步:
①该类实现java.lang.Cloneable接口。
②覆盖Object的clone方法,将访问修饰符改为public,修改返回类型。
③clone方法内部调用父类的clone方法。
示例如下:
public class CloneableObj implements Cloneable{
//...
@Override
public CloneableObj clone(){
try {
return (CloneableObj) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
按照Object克隆方法描述中的规范,克隆得到对象应该独立于被克隆对象,从根本上说,它们的内存地址(不严格的说)应该不同。但是如果有这种情况,一个可克隆类中包含其他的引用类型属性,那么克隆后会是什么情况呢?举个例子说明一下:
public class MyObj implements Cloneable{
private MyPro myPro;
public MyPro getMyPro() {
return myPro;
}
public void setMyPro(MyPro myPro) {
this.myPro = myPro;
}
@Override
public MyObj clone(){
try {
return (MyObj) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class MyPro {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
MyObj myObj = new MyObj();
MyPro myPro = new MyPro();
myPro.setName("aa");
myObj.setMyPro(myPro);
MyObj clone = myObj.clone();
//myObj和clone是否指向相同的内存地址
System.out.println(clone == myObj);
//myObj的myPro和clone的myPro是否指向相同的内存地址
System.out.println(clone.getMyPro() == myObj.getMyPro());
}
}
运行结果:
false
true
可见,虽然克隆对象与被克隆对象内存地址不同,但他们的引用类型属性却指向相同的内存地址。这便涉及到了
浅克隆和
深克隆的问题。
上面的情况就是浅克隆,可见clone方法默认的行为就是浅克隆。那么也很容易想到,如果克隆一个对象,并递归的克隆其所有的引用类型属性,这样的方式就是深克隆了。
对于一个复杂的类,如果需要提供克隆方法的话,一般会先调用父类的clone方法,然后将相应的域(属性)设置为相应的值(有些域需要保存状态,有些域需要清空。)或者将域清空然后调用内部方法来初始化状态,并且要在方法注释上说明该方法的行为是深克隆还是浅克隆。如HashMap的克隆方法:
/**
* Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
* values themselves are not cloned.
*
* @return a shallow copy of this map
*/
public Object clone() {
HashMap<K,V> result = null;
try {
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// assert false;
}
result.table = new Entry[table.length];
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
return result;
}