大家都知道在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():
protected native Object clone() throws CloneNotSupportedException;
仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
为什么要克隆?
大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的。扯了这么多废话,终于进入正题了ORZ。
如何实现克隆
简单到你不敢相信。直接在你的类的后面声明implements Cloneable。关于这个接口,它的源码如下:
public interface Cloneable {
}
可以看到它是一个空的接口,它的作用就是做标记。如果没有实现Cloneable接口就直接使用clone方法,程序会抛出CloneNotSupportedException异常。
然后是重写clone方法,并修改成public访问级别。举个经典的栗子:
class Outer implements Cloneable {
public int name;
public Inner inner;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args){
Outer o_one = new Outer();
o_one.inner = new Inner("zhangsan");
try {
Object obj = o_one.clone();
Outer o_two = (Outer)obj;
System.out.println(o_one==o_two); //打印false
System.out.println(o_one.inner.name.equals(o_two.inner.name)); //打印true
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
上面的代码实现的其实是浅克隆,浅克隆对于引用类型仅拷贝引用,没有真正地让两个对象独立开来互相之间没有任何关系。由于是浅克隆,使得imp2修改了child的某个属性后会是的imp1中child的属性也跟着改变。或者比较两个对象的地址:imp1.child==imp2.child,返回的结果是true。但是,如果一个对象只包含原始数据域或者不可变对象域(比如String类型),推荐使用浅克隆。
深克隆
类中的所有引用类型做一些修改,让它也实现Cloneable接口。然后修改本类中的clone方法:
public class Inner implements Cloneable{
public String name;
public Child(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值为:" + name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
修改本类的clone方法:
static class Outer implements Cloneable {
public int count;
public Inner inner;
@Override
public Object clone() throws CloneNotSupportedException {
Outer obj = (Outer)super.clone();
obj.inner = (Inner) inner.clone();
return obj;
}
}
画重点了:
- 需要重写clone方法,不仅仅只调用父类的方法,还需调用属性的clone方法;
- 对象之间100%数据分离
- 如果是对象存在引用类型的属性,建议使用深克隆
- 深克隆比浅克隆要更加耗时,效率更低
解决多层克隆问题
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
public Outer myclone() {
Outer outer = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
Inner也必须实现Serializable,否则无法序列化:
public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值为:" + name;
}
}
这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。
Tips
- 在克隆方法中,如果我们需要对可变对象的final域也进行拷贝,由于final的限制,所以实际上是无法编译通过的。因此为了实现克隆,我们需要考虑舍去该可变对象域的final关键字。
- 如果你决定用线程安全的类实现Cloneable接口,需要保证它的clone方法做好同步工作。默认的Object.clone方法是没有做同步的。