在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在 Java语言中,用简单的赋值语句(简单赋值是引用赋值还是指向同一个块地址)是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。
要说明的有两点:
一是拷贝对象返回的是一个新对象,而不是一个引用。
二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象具有的信息,而不是对象的初始信息。
关于Clone()方法的使用注意如下几点即可:
a. 什么时候使用shallow Clone,什么时候使用deep Clone,这个主要看具体对象的域是什么性质的,基本型别还是reference variable
b. 覆盖Clone()方法的对象所属的类(Class)必须implements Clonable接口,否则在调用Clone方法的时候会抛出CloneNotSupportedException。
我们用clone方法一般我们的诉求是获得一个和原来对象一样的实例但不同地址的实例,同时属性值一样。clone方法有一个缺点是只是字段到字段的赋值,这就造成了一个自定义类的属性如果不是基本数据类型,而是自定义类或者jdk的复杂类或者数组。那么clone方法也不能完全拷贝一个全新的副本给你。这个时候必须自己手动对自定义类的属性拷贝。这就是所谓的深度clone。
以Employee为例,它里面有一个域hireDay不是基本型别的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型别的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的,过程下图所示:
深度clone
class Employee implements Cloneable
{
public Object clone() throws CloneNotSupportedException
{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();//自动给原来的引用clone一个新的副本
return cloned;
}
}
这个是简单的浅克隆,对于基本数据类型是可以的
class CloneClass implements Cloneable{
public int aInt;
public Object clone(){
CloneClass o = null;
try{
o = (CloneClass)super.clone(); //调用object类的clone原生方法对内存块数据字段到字段的赋值
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
有三个值得注意的地方。
一、是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang 包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。
二、另一个值得请注意的是重载了clone()方 法。
三、最后在clone()方法中调用了super.clone(),完成了内存块的新建并赋值功能。这也意味着无论clone类的继承结构是什么样的,super.clone()直接或 间接调用了java.lang.Object类的clone()方法。
clone类为什么还要实现 Cloneable接口呢? Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了 super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。