clone简介
-
即复制一个对象出来,他存在
Object
类中,是一个本地方法,并且修饰符是protected
,所以他是可以让其子类实现自己的clone方法的protected native Object clone() throws CloneNotSupportedException;
与new的区别
- new是从头创建一个对象的过程,为新对象分配内存,首先看new的对象中的属性类型需要多大空间,根据空间创建内存区域,分配完成后进行初始化值,然后返回对象供外部引用
- clone也是为对象分配内存,首先看new的对象中的属性类型需要多大空间,根据空间创建内存区域,不同的是他的初始值是从被clone对象中读取的,之后返回对象供外部引用
什么情况下用clone
- 比如对象A已经是满足我们的要求了,那么我们希望有一个实例的属性值是跟A是一致的,那么我们就有两种办法将B实例给弄出来,第一种就是new,然后自己一个个set值,而另外一个就是使用clone方法,这种方法简单高效,在这种场合下是比较实用的,不过需要主要clone中的一些问题,即深克隆和浅克隆的区别
简单使用
-
使用clone方法,需要被clone的对象实现
Cloneable
接口,否则会有异常CloneNotSupportedException
,但是实现的这个接口中并没有实现任何方法,只是一个标记接口的作用public class TargetClass implements Cloneable{ int id; long timestamp; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() {...} }
-
然后测试
public class Tests { public static void main(String[] args) throws CloneNotSupportedException { TargetClass target = new TargetClass(); target.id = 1; target.timestamp = System.currentTimeMillis(); TargetClass target2 = target; Object o = target.clone(); System.out.println(target); System.out.println(target2); System.out.println(o); target.id = 2; System.out.println("--------------------"); System.out.println(target); System.out.println(target2); System.out.println(o); } }
-
结果
TargetClass{id=1, timestamp=1548131568371} TargetClass{id=1, timestamp=1548131568371} TargetClass{id=1, timestamp=1548131568371} -------------------- TargetClass{id=2, timestamp=1548131568371} TargetClass{id=2, timestamp=1548131568371} TargetClass{id=1, timestamp=1548131568371}
- 从中我们可以看到.当使用
=
直接赋值对象的时候,是直接将引用赋值给此对象的,所以当target对象更改的时候,target2对象也会受到影响,他俩的关系就类似是这样的
- 而使用clone方法返回的对象,当target对象更改的时候,并没有收到任何影响,因为他们是两个不同的对象,就比如下面
-
我们也可以将toString注释掉,查看他们的地址值,发现可以证实我们的说法是正确的
TargetClass@677327b6 TargetClass@677327b6 TargetClass@14ae5a5 -------------------- TargetClass@677327b6 TargetClass@677327b6 TargetClass@14ae5a5
-
我们再看一个例子,这时候TargetClass对象发生一点改变
public class TargetClass implements Cloneable{ int id; long timestamp; InnerClass innerClass; class InnerClass{ long innerClassNumber; public InnerClass(long innerClassNumber) { this.innerClassNumber = innerClassNumber; } } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() {...} }
-
测试
public static void main(String[] args) throws CloneNotSupportedException { TargetClass target = new TargetClass(); target.id = 1; target.timestamp = System.currentTimeMillis(); target.innerClass = new TargetClass().new InnerClass(1); TargetClass target2 = target; Object o = target.clone(); System.out.println(target); System.out.println(target2); System.out.println(o); target.id = 2; target.innerClass.innerClassNumber = 2; System.out.println("--------------------"); System.out.println(target); System.out.println(target2); System.out.println(o); }
-
结果
TargetClass{id=1, timestamp=1548132881258, innerClass=1} TargetClass{id=1, timestamp=1548132881258, innerClass=1} TargetClass{id=1, timestamp=1548132881258, innerClass=1} -------------------- TargetClass{id=2, timestamp=1548132881258, innerClass=2} TargetClass{id=2, timestamp=1548132881258, innerClass=2} TargetClass{id=1, timestamp=1548132881258, innerClass=2}
- 从中我们发现除了在TargetClass中的id属性跟之前测试的结果是一样的,其中我们发现他的内部类InnerClass,更改之后三个对象就全部收到影响的,clone方法不是可以重新创建对象,而clone出来的对象不受影响吗,现在的引用关系就如下
- 如图可以看出,三个对象的内部类其实是引用的同一个对象,我们之前说了,clone创建对象,只是将被clone对象的值取出来赋值到新对象,所以这是一个
=
操作,那不就是拿出innerClassNumber
吗,其实不是的,只是将InnerClass对象的引用赋值给了新clone的对象,所以造成了三个实例引用一个innerClass实例,就造成了,更改一个值,三个实例收到影响 -
为了避免上面的都受影响的情况,即clone出来的对象不受target实例改变的影响,我们就得再次重写实现TargetClass的clone方法
@Override protected Object clone() throws CloneNotSupportedException { TargetClass targetClass = new TargetClass(); targetClass.id = this.id; targetClass.timestamp = this.timestamp; targetClass.innerClass = this.new InnerClass(this.innerClass.innerClassNumber); return targetClass; }
-
如上就可以实现了clone出来的对象不受target实例改变的影响了,测试结果如下
TargetClass{id=1, timestamp=1548134335063, innerClass=1} TargetClass{id=1, timestamp=1548134335063, innerClass=1} TargetClass{id=1, timestamp=1548134335063, innerClass=1} -------------------- TargetClass{id=2, timestamp=1548134335063, innerClass=2} TargetClass{id=2, timestamp=1548134335063, innerClass=2} TargetClass{id=1, timestamp=1548134335063, innerClass=1}
-
更简单的一种实现InnerClass的clone方法,就是InnerClass也实现Cloneable接口,然后更改TargetClass的clone方法
public class TargetClass implements Cloneable{ int id; long timestamp; InnerClass innerClass; class InnerClass implements Cloneable{ long innerClassNumber; public InnerClass(long innerClassNumber) { this.innerClassNumber = innerClassNumber; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Override protected Object clone() throws CloneNotSupportedException { TargetClass clazz = (TargetClass) super.clone(); clazz.innerClass = (InnerClass) innerClass.clone(); return clazz; } @Override public String toString() {...} }
- 所以这就是一个类中存在引用对象的时候我们如果需要这个引用对象也clone出来,我们就必须手动去重写clone方法
- 到这我们就可以总结一下,对于基本类型,clone方法是可以对被clone对象实现一个副本的,而如果类中出现了引用类型,就必须手动去重写clone方法了
深克隆与浅克隆
- 前面我们有了一些认识,所以我们来说一下深克隆与浅克隆,首先谈一下浅克隆,即Java中的默认实现就是浅克隆,我们之前的简单使用中可以看到,当存在引用对象的时候,默认实现的clone方法是不能进行操作引用对象的,所以他只是克隆一个被clone类本类的一些基本类型和变量引用,即只克隆了一层,如下
- 而如果要进行引用对象的clone,那么这就是深克隆,这就必须由我们手动实现逻辑代码来操作克隆过程,之前的简单使用中,我们可以看到,增添了一个引用类型就需要更改clone方法,所以如果有很多引用对象,而且引用是递归深层引用的,即
A->B->C->D->...
,那么我们就需要在A的clone方法中实现对B.C.D的深层引用clone,所以在复杂业务中实现深克隆并不是很容易的,所以深克隆即完全克隆,基本上是不可能实现的