本节我们会讨论 Cloneable 接口,这个接口指示一个类提供了一个安全的 clone() 方法。
Object 类提供的 clone() 方法是 “浅拷贝”,并没有克隆对象中引用的其他对象,原对象和克隆的对象仍然会共享一些信息。深拷贝指的是:在对象中存在其他对象的引用的情况下,会同时克隆对象中引用的其他对象,原对象和克隆的对象互不影响。
要了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。原变量和副本都是同一个对象的引用(见图 6-1)。这说明,任何一个变量改变都会影响另一个变量。
Employee original = new Employee("John Public", 50000);
Employee copy = original;
copy.raiseSalary(lO); // oops-also changed original
如果希望 copy 是一个新对象,它的初始状态与 original 相同,但是之后它们各自会有自己不同的状态,这种情况下就可以使用 clone() 方法。
Employee copy = original.clone();
copy.raiseSalary(lO); // OK original unchanged
不过并没有这么简单。clone() 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象(Object 类不可以克隆 Employee 类)。这个限制是有原因的。想想看 Object 类如何实现 clone()。Object 类它对于这个对象一无所知,所以只能逐个域地进行拷贝。如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题、但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
class Employee {
// instance fields
private String name;
private double salary;
private Date hireDay;
// constructor
public Employee(String n, double s, int year, int month, int day) {
this.name = n;
this.salary = s;
this.hireDay = new Date(year, month, day);
}
// a method
public String getName() {
return name;
}
// more methods
}
图 6-2 显示了使用 Object 类的 clone() 方法克隆一个 Employee 对象会发生什么。可以看到,默认的克隆操作是 “浅拷贝”,并没有克隆对象中引用的其他对象。
浅拷贝会有什么影响吗?这要看具体情况。
对于每一个类,需要确定:
实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:
Cloneable 接口的出现与接口的正常使用并没有关系。具体来说,Cloneable 接口没有指定 clone() 方法,clone() 方法是从 Object 类继承的。Cloneable 接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很 “偏执”,如果一个对象请求克隆,但没有实现 Cloneable 接口,就会生成一个受检异常(CloneNotSupportedException 异常)。
如果在一个对象上调用 clone() 方法,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone() 方法就会拋出一个 CloneNotSupportedException。
注释:Cloneable 接口是 Java 提供的一组标记接口(tagging interface)之一。应该记得:
if (obj instanceof Cloneable) {}
建议你自己的程序中不要使用标记接口。
即使 clone() 的默认(浅拷贝)实现能够满足要求,还是需要实现 Cloneable 接口,将 clone() 方法重新定义为 public, 再调用 super.clone()。下面给出一个例子:
class Employee implements Cloneable {
// raise visibility level to public, change return type
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
}
与 Object.clone() 提供的浅拷贝相比,前面看到的 clone() 方法并没有为它增加任何功能。这里只是让这个方法是公有的。要建立深拷贝,还需要做更多工作,克隆对象中可变的实例域。下面来看创建深拷贝的 done() 方法的一个例子:
class Employee implements Cloneable {
// ...
public Employee clone() throws CloneNotSupportedException {
// call Object.clone()
Employee cloned = (Employee) super.clone();
// clone mutable fields
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
如果在一个对象上调用 clone() 方法,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone() 方法就会拋出一个 CloneNotSupportedException。当然,Employee 和 Date 类实现了 Cloneable 接口,所以不会抛出这个异常。不过, 编译器并不了解这一点,因此,我们声明了这个异常。
捕获这个异常是不是更好一些?
public Employee clone() {
try {
Employee cloned = (Employee) super.clone();
// ...
return cloned;
} catch (CloneNotSupportedException e) {
return null;
}
// this won't happen, since we are Cloneable
}
捕获异常这非常适用于 final 类。否则,最好还是保留 throws 说明符。这样就允许子类在不支持克隆时选择抛出一个 CloneNotSupportedException()。
必须当心子类的克隆。例如,一旦为 Employee 类定义了 clone() 方法,任何人都可以用它来克隆 Manager 对象(Manager 类是 Employee 类的子类)。Employee 克隆方法能完成工作吗?这取决于 Manager 类的域。在这里是没有问题的,因为 bonus 域是基本类型。但是 Manager 可能会有需要深拷贝或不可克隆的域。不能保证子类的实现者一定会修正 clone() 方法让它正常工作。出于这个原因, 在 Object 类中 clone() 方法声明为 protected。不过,如果你希望类用户调用 clone(),就不能这样做。
《Java核心技术卷一:基础知识》(第10版)第 6 章:接口、lambda 表达式与内部类 6.2.3 对象克隆
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。