Java中的对象都有一个方法叫clone(),是从Object类中继承而来的,它的作用是复制一个对象并返回,除了在堆中的地址不一样以外,对象里的实例变量、方法都与原对象完全一致。调用该方法前必须先重写clone(),而重写则需要先实现Cloneable接口,直接重写会抛出CloneNotSupportedException异常。下面举个例子:
class person implements Cloneable{
private int age;
private Stringname;
private hat hat;
person(int age,String name,hat hat){
this.age = age;
this.name = name;
this.hat = hat;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public hat getHat() {
return this.hat;
}
public void setHat(hat h) {
this.hat = h;
}
public person clone() throws CloneNotSupportedException {
return (person)super.clone();
}
}
class hat{
private Stringcolor;
hat(Stringcolor){
this.color = color;
}
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return this.color;
}
}
这是一个很简单的类,但举例完全足够了。类里面有一个int表示年龄,一个String表示名字,有一个hat类表示各种颜色的帽子,另外提供了set和get方法。重点是person类现在已经实现了cloneable接口,可以重写clone()了,重写也非常简单,只需要直接调用父类的方法即可返回克隆对象,这时需要捕捉或者抛出CloneNotSupportedException异常。
下面是一些测试:
personp1 = new person(18,"xiaomi",new hat("black"));
personp2 = p1.clone();
System.out.println(p1==p2);
System.out.println(p1.getAge()==p2.getAge());
System.out.println(p1.getName()==p2.getName());
System.out.println(p1.getHat()==p2.getHat());
这是输出结果:
false
true
true
true
可以看到两个对象除了在堆中的地址不一样以外,其实例变量和方法都完全一致。但这只是一种浅拷贝,我们来看下面的测试:
p1.getHat().setColor("pink");
System.out.println(p1.getHat().getColor());
System.out.println(p2.getHat().getColor());
测试结果:
pink
pink
将p1中的hat对象修改成pink后,p2的hat对象也同时被修改了,这显然不是我们想要的结果。如果细心看上面的代码后就会发现,clone()返回的克隆对象中对象引用的都是同一个对象,如果我们通过引用直接修改对象,就相当于修改持有所有浅拷贝得来的对象中的对象引用。如果要改变这一种情况,则需要深拷贝。
如果你已经看懂了上面的浅拷贝,那么深拷贝其实很好理解,就是将类中的所有对象引用的类都实现浅拷贝。Person类中有一个hat类(String是一个不可变对象,任何对String的修改都会重新获得一个新的String对象,在这里我们可以将它视为基本类型),那么我们只需要将hat类也拷贝一份并将克隆对象的引用修改后再返回即可。代码如下:
publicObject clone() throws CloneNotSupportedException {
person newPerson = (person) super.clone();
newPerson.hat = hat.clone();
return newPerson;
}
要调用hat的clone(),那么首先hat也必须实现Cloneable接口并重写clone()。代码如下:
public hatclone() throws CloneNotSupportedException {
return (hat) super.clone();
}
我们来测试一下:
p1.getHat().setColor("pink");
System.out.println(p1.getHat().getColor());
System.out.println(p2.getHat().getColor());
测试结果:
pink
black
现在已经完全符合我们的需求了,但是还有一个问题就是每次深拷贝的时候,要求将类中的所有对象引用的类都实现Cloneable接口并重写Clone(),在非常大的工程项目中,引用关系变得十分复杂,如果需要实现对一个对象Clone会变得异常繁琐甚至是无法做到,但如果不使用深拷贝则会在某些情况下由于引用关系出错,因此sun也将该方法隐藏起来,我们使用clone()前一定要谨慎使用,并且根据实际场景选择性地使用这两种不同的拷贝方式。
如果你有更好的方法欢迎在评论区留言,欢迎点赞转发,感谢阅读。