前言
在Java程序开发中,有时候需要我们得到另一个对象的克隆,在进行克隆时,我们往往希望得到的克隆对象里的数据和原对象一模一样,但是对其中任何一个对象的更改都不会影响另一个对象,也就是克隆之后,两个对象完全无关。对象的克隆并不是简单的一条赋值语句就可以,接下来简单探讨一下Java的对象克隆(拷贝)。
1.没拷贝
先看一下最简单的写法:
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(){
return "name:"+name + " age:" + age;
}
}
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException{
Person per = new Person("testClone",18);
Person perClone = per;
perClone.setAge(19);
System.out.println("原对象:"+per);
System.out.println("克隆对象:"+perClone);
}
}
如代码,定义了一个Person类,具有name和age属性,新建了一个Person对象per,用语句Person perClone = per;来企图进行拷贝,再把克隆对象的age改为19,结果自然是不尽如人意
由于Java的对象变量保存的是引用,Person perClone = per;顶多是给原对象加了个别名,完全没起到拷贝的作用。
2.浅拷贝
Object类里提供了一个clone方法,可以用来进行对象的克隆。
可以看出,Object自带的方法是一个native方法,native方法是底层方法,这里不多介绍了。这个clone方法的机理是返回一个新的对象,对对象里的数据进行逐个的赋值。我们在Person类中加入如下代码:
@Override
public Person clone() throws CloneNotSupportedException{
return (Person)super.clone();
}
由于Object里的clone方法是protected类型,无法用对象直接调用,必须先在类中重写,将其改为public权限,就可以随意调用。注意的是方法需要抛出一个CloneNotSupportedException,否则会报如下的错:
同时,需要实现拷贝功能的类也要实现Cloneable接口,否则也会报同样的错,此接口定义如下:
public interface Cloneable {
}
可以看出,接口里没有任何东西,这是个标记接口,但是不实现就是无法使用拷贝功能。我们把主方法改成如下:
public static void main(String[] args) throws CloneNotSupportedException{
Person per = new Person("testClone",18);
Person perClone = per.clone();
perClone.setAge(19);
perClone.setName("clone");
System.out.println("原对象:"+per);
System.out.println("克隆对象:"+perClone);
}
咦?这么一看,这不是达到了我们想要的效果了吗?对克隆对象的修改没有影响到原对象啊。这种方式是不是就够了?
首先,age是基本数据类型,采用赋值进行克隆的操作是没问题的;其次,String类是不可变类,对字符串的更改最终结果是使得String类对象变量指向另一个字符串,String类对象存储的是一个字符串的引用,对对象的浅拷贝操作,使得perClone对象中的name也指向"testClone"字符串,之后的perClone.setName("clone"),令perClone对象中的name指向"clone"字符串,所以这种方式看似达成了目标。
我们可以向Person类中再加一个属性pet,pet是Pet类的对象,这是个可变类。
class Pet{
String type;
public Pet(String type) {
this.type = type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString(){
return this.type;
}
}
定义一个Pet类,给Person类加上一个属性private Pet pet以及对应的set和get方法。主函数修改如下:
Person per = new Person("testClone",18,new Pet("cat"));
Person perClone = per.clone();
perClone.setAge(19);
perClone.setName("clone");
perClone.getPet().setType("dog");
System.out.println("原对象:"+per);
System.out.println("克隆对象:"+perClone);
我们可以发现,对克隆对象的修改影响了原对象,说明这种浅拷贝是不能达到效果的,还是不能让原对象和克隆对象完全无关。。。
简单画出两个对象的属性关系,就不难理解为啥一个对象对pet的改变会影响到另一个对象了。可见,使用Object类里的clone有些时候并不能满足我们的要求。。。
3.深拷贝
由于pet是一个可变对象,所以它也需要被拷贝,而不是简单的赋值,对Pet类代码做出修改:
class Pet implements Cloneable{
String type;
public Pet(String type) {
this.type = type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString(){
return this.type;
}
@Override
public Pet clone() throws CloneNotSupportedException{
return (Pet)super.clone();
}
}
现在,pet类也具有了克隆的能力,再将Person类中的clone方法修改如下:
@Override
public Person clone() throws CloneNotSupportedException{
Person cloned = (Person)super.clone();
cloned.pet = pet.clone();
return cloned;
}
再次运行程序,结果如下:
现在才是真正的完成了克隆,达到了原对象和克隆对象的修改不会相互影响的效果。此时的属性关系图为:
总结
1.对对象进行克隆时,先检查Object类里的clone方法是否可用,如果对象里的属性都是基本数据类型,或者String类这种不可变类的对象,那么可以直接调用Object类的clone方法
2.如果对象里有可变类的属性,就需要重写Object类的clone方法
3.想调用clone方法,必须实现Cloneable接口,并抛出CloneNotSupportedException