Cloneable克隆
现有两个类People以及Student,他们之间是继承关系
class People
{
private String name;
private int age;
private String[] parents = new String[2];
public People (String name, int age, String[] parents)
{
this.name = name;
this.age = age;
this.parents = parents;
}
public String getName ()
{
return this.name;
}
public int getAge ()
{
return this.age;
}
public String[] getParents()
{
return this.parents;
}
public void setName (String name)
{
this.name = name;
}
public void setAge (int age)
{
this.age = age;
}
public void setParents (String parentName, int index)
{
this.parents[index] = parentName;
}
}
class Student extends People
{
private String school;
public Student (String name, int age, String[] parents, String school)
{
super(name, age, parents);
this.school = school;
}
public void setSchool (String school)
{
this.school = school;
}
public String getSchool ()
{
return this.school;
}
}
当对象引用的变量建立副本时,原变量以及副本都是同一个对象的引用。
var original = new People("Paul", 35, new String[]{"Green", "Pink"});
var copy = original;
copy.setAge(36); // also changed original
如果希望copy是一个新对象,它的初始状态与original相同,但是之后它们各自会有自己不同的状态,这种情况下就应该使用clone方法。
但clone方法是Object的一个protected方法,说明这里我不可以直接调用这个方法。只有People类内部可以调用。(这个限制是有原因的,后面会解释)。
浅拷贝
Object提供的clone是使用浅拷贝,浅拷贝并不会克隆对象中引用的其他对象。如果对象中的所有数据字段都是数值或其他基本类型,拷贝这些字段没有任何问题。但是如果对象包含子对象的引用,拷贝字段就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
下面是在People中实现浅拷贝的代码:
class People implements Cloneable
{
......
public People clone () throws CloneNotSupportedException
{
return (People) super.clone();
}
}
在这里,Cloneable接口的出现与接口的正常使用并没有关系。具体来说,它没有指定clone方法,这个方法是从Object类继承。这个接口只是作为一个标记,指示类设计者了解克隆过程。对象对克隆很“偏执”,如果一个对象请求克隆,但是么有实现这个接口,就会生成一个检查型异常。(标记接口不包含任何方法;它唯一的作用就是允许在类型查询中使用instanceof
)if (obj instanceof Cloneable)...
如果原对象和克隆对象没有共享的子对象,或原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。如子对象属于一个不可变的类,如String,就是这种情况。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没办法会生成它的引用,这种情况同样是安全的。
但我们这里有一个子对象引用String[],看看会出现什么情况
var original = new People("Paul", 35, new String[]{"Green", "Pink"});
var copy = original.clone();
copy.setAge(36);
copy.setParents("Smart", 0);
copy.setParents("Love", 1);
System.out.println(original.getAge()); // 35
System.out.println(Arrays.toString(original.getParents())); // [Smart, Love]
深拷贝
可以看出,copy修改了parents,也会影响到original的parents。所以,对于这种含可变子对象的情况,我们应该使用深拷贝,同时克隆所有子对象。
我们修改clone方法,完成深拷贝。
public People clone () throws CloneNotSupportedException
{
People p = (People) super.clone();
p.parents = this.parents.clone();
return p;
}
至此,深拷贝就完成了。上面的测试代码不会再打印[Smart,Love]而是[Green, Pink]。
知识扩展:所有数组类型都有一个公共的clone方法,而不是受保护的。可以用这个方法建立一个新数组,包含原数组所有元素的副本。
克隆与继承
必须当心子类的克隆。例如,一旦People类定义了clone方法,任何人都可以用它来克隆Student对象。People克隆方法可以完成工作么?这取决于Student字段,这里是没问题的,因为Student不含可变子对象。但是Student可能会有需要深拷贝或不可克隆的字段。不能保证子类的实现者一定会修正clone方法让它正常工作。处于这个原因,在Object类中的clone方法声明为protected。不过,如果你希望类用户调用clone,就不能这么做。