首先我们来看下clone方法的源码
protected native Object clone() throws CloneNotSupportedException;
通过源代码可以发现几点:
- clone方法是native方法,native方法的效率远高于非native方法,因此还是使用clone方法去做对象的拷贝而不是使用new的方法,copy。
- 此方法被protected修饰。这就意味着想要使用,必须继承它(废话,默认都是继承的)。然后重载它,如果想要使得其他类能使用这个类,需要设置成public。
- 返回值是一个Object对象,所以要强制转换才行。
测试clone方法,代码如下:
public class TestReen{
public static void main(String[] args) throws Exception{
TestReen tReen = new TestReen();
TestReen copy = (TestReen)tReen.clone();
}
}
但是运行时发现抛出异常了。
Exception in thread "main" java.lang.CloneNotSupportedException: com.test.count.TestReen
at java.lang.Object.clone(Native Method)
at com.test.count.TestReen.main(TestReen.java:11)
进入clone方法,看注释发现如下信息:表明此类不支持Cloneable接口的话就会报错。
@exception 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.
去看看Cloneable接口,竟然是个空的接口
public interface Cloneable {
}
总结其注释的话,差不多三点:
- 此类实现了Cloneable接口,以指示Object的clone()方法可以合法地对该类实例进行按字段复制;
- 如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupporteddException;
- 按照惯例,实现此接口的类应该使用公共方法重写Object的clone()方法,Object的clone()方法是一个受保护的方法;
因此想实现clone的话,除了继承Object类外,还需要实现Cloneable接口;
创建并返回此对象的一个副本。对于任何对象x,表达式:
- x.clone() != x为true
- x.clone().getClass() == x.getClass()为true
- x.clone().equals(x)一般情况下为true,但这并不是必须要满足的要求
测试以下例子:
public class TestReen implements Cloneable{
private int id;
private static int i = 1;
public TestReen() {
System.out.println("i = " + i);
System.out.println("执行了构造函数"+i);
i++;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public static void main(String[] args) throws Exception{
TestReen t1 = new TestReen();
t1.setId(1);
TestReen t2 = (TestReen)t1.clone();
TestReen t3 = new TestReen();
System.out.println("t1 id: "+ t1.getId());
System.out.println("t2 id: "+ t2.getId());
System.out.println("t1 == t2 ? " + (t1 == t2));
System.out.println("t1Class == t2Class ? " + (t1.getClass() == t2.getClass()));
System.out.println("t1.equals(t2) ? " + t1.equals(t2));
}
}
结果如下,有几个发现:
- 构造函数除了new执行以外,clone并没有调用到构造函数,也就是clone方法是不走构造方法的。
- t1 和 t2 是不等的,说明指向了不同的堆地址空间,是两个对象。
- getClass是相同的,getClass是什么?是获取这个实例的类型类,有点拗口,其实就是TestReen类型,可见clone出来的类型还是一样的。
i = 1
执行了构造函数1
i = 2
执行了构造函数2
t1 id: 1
t2 id: 1
t1 == t2 ? false
t1Class == t2Class ? true
t1.equals(t2) ? false
克隆分为浅克隆(shallow clone)和 深克隆(deep clone)。
- 浅克隆:Object的clone提供的就是浅克隆,由下面的例子可以看见,只克隆了自身对象和对象内实例变量的地址引用,它内部的实例变量还是指向原先的堆内存区域。
- 深克隆:克隆所有的对象,包括自身以及自身内部对象。
我们来看下什么是浅克隆,什么是深克隆。例如我有一个Person类:
public class Person implements Cloneable{
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person() {}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:
我们先来看下一个浅克隆的例子:
public class TestReen implements Cloneable{
private String name;
private SonReen sonReen;
public TestReen(String name, SonReen sonReen) {
this.name = name;
this.sonReen = sonReen;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SonReen getSonReen() {
return sonReen;
}
public void setSonReen(SonReen sonReen) {
this.sonReen = sonReen;
}
public static void main(String[] args) throws Exception {
SonReen sonReen = new SonReen("abc");
TestReen t1 = new TestReen("李四", sonReen);
TestReen t2 = (TestReen) t1.clone();
System.out.println("t1==t2 ? " + (t1 == t2));
System.out.println("t1.name==t2.name ? " + (t1.getName() == t2.getName()));
System.out.println("t1.sonReen==t2.sonReen ? " + (t1.getSonReen() == t2.getSonReen()));
System.out.println("t1.sonReen.SonName==t2.sonReen.SonName ? "
+ (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
System.out.println("========================================");
t1.getSonReen().setSonName("王五");
System.out.println(t1.getSonReen().getSonName());
System.out.println(t2.getSonReen().getSonName());
}
}
class SonReen{
private String sonName;
public SonReen(String sonName) {
super();
this.sonName = sonName;
}
public String getSonName() {
return sonName;
}
public void setSonName(String sonName) {
this.sonName = sonName;
}
}
结果如下:
t1==t2 ? false
t1.name==t2.name ? true
t1.sonReen==t2.sonReen ? true
t1.sonReen.SonName==t2.sonReen.SonName ? true
========================================
王五
王五
我们从结果看出,只有t1和t2指向的是不同的地址,而t1的sonReen和t2的sonReen指向的是同一块地址,说明浅克隆在克隆一个类的引用类型的成员变量时,克隆的是引用。并且我们修改t1的sonReen的Sonname值的时候,我们发现t2的这个值也被修改了,这更加说明 t1的sonReen和t2的sonReen指向的是同一块地址。
深克隆一般有两种实现方式:
- 让类实现序列化接口Serializable。然后对对象进行序列化操作,然后反序列化得到对象。
- 先调用super.clone()方法克隆出一个新对象来,然后再调用子对象的clone方法实现深度克隆。
我们先看第一种:
public class TestReen implements Serializable {
private String name;
private SonReen sonReen;
public TestReen(String name, SonReen sonReen) {
this.name = name;
this.sonReen = sonReen;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SonReen getSonReen() {
return sonReen;
}
public void setSonReen(SonReen sonReen) {
this.sonReen = sonReen;
}
public Object deepClone() throws Exception {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bo);
out.writeObject(this);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return oi.readObject();
}
public static void main(String[] args) throws Exception {
SonReen sonReen = new SonReen("abc");
TestReen t1 = new TestReen("李四", sonReen);
TestReen t2 = (TestReen) t1.deepClone();
System.out.println("t1==t2 ? " + (t1 == t2));
System.out.println("t1.name==t2.name ? " + (t1.getName() == t2.getName()));
System.out.println("t1.sonReen==t2.sonReen ? " + (t1.getSonReen() == t2.getSonReen()));
System.out.println("t1.sonReen.SonName==t2.sonReen.SonName ? "
+ (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
System.out.println("========================================");
t1.setName("王五");
System.out.println(t1.getName());
System.out.println(t2.getName());
}
}
class SonReen implements Serializable {
private String sonName;
public SonReen(String sonName) {
super();
this.sonName = sonName;
}
public String getSonName() {
return sonName;
}
public void setSonName(String sonName) {
this.sonName = sonName;
}
}
结果为:
t1==t2 ? false
t1.name==t2.name ? false
t1.sonReen==t2.sonReen ? false
t1.sonReen.SonName==t2.sonReen.SonName ? false
========================================
王五
李四
我们从结果可以看到,通过序列化和反序列化。克隆出来的t2与t1并不是一个对象,并且t2和t1内部的引用类型的成员变量也得到了克隆,不再是想浅度克隆那样克隆引用。并且我们还可以发现,通过修改t1的name属性,t2的name属性并没有发生变化。更加说明了t1的name和t2的name指向了不同的内存单元。这就是深度克隆。
第二种:
class Body implements Cloneable {
public Head head;
public Body() {
}
public Body(Head head) {
this.head = head;
}
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
}
class Head implements Cloneable {
public Head() {
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestReen {
public static void main(String[] args) throws CloneNotSupportedException {
Body body1 = new Body(new Head());
Body body2 = (Body) body1.clone();
System.out.println("body1 == body2 : " + (body1 == body2));
System.out.println("body1.head == body2.head : " + (body1.head == body2.head));
}
}
结果如下:
body1 == body2 : false
body1.head == body2.head : false
从结果我们看出这也是一个深度克隆,但是要注意的是,如果Head中假设有一个Face类型,如要深度克隆,那么这个Face类型也要实现Cloneable接口并且重写clone方法。