对象复制(Object Cloning)是一个重要的特性,也是一个复杂的过程。Java语言的设计者最初希望对象通过Object类提供的clone()成员方法复制出新的对象。因为clone()是成员方法,开发人员可以覆盖(Override) clone()以实现个性化的需求(Customization)。然而,由于Java语言设计上的失误,导致了这种对象复制机制饱受批评,甚至在一些代码库中,开发人员已不再使用clone()成员方法复制对象。
本章首先将逐一介绍对象复制的概念和使用细节。在理解了繁琐的细节之后,本章还会指出对象复制机制在设计上的一些问题,以及列举一些构造或者复制对象的替代方法。
在Java语言对象复制机制中,Object类提供了clone()成员方法。该方法是protected,因此,在类(或者基类、子类)的外部是不能直接使用该类的clone()方法的。Object类中的clone()方法(即:默认的clone()方法)实现了两个目的。其一,生成出一个与this对象相同类型的对象;其二,依次拷贝this对象中的成员变量的值至新的对象中。因此,clone()方法满足以下测试。
在使用clone()方法之前,需要提供对象复制功能的类还需要实现Cloneable接口。如果类没有实现Cloneable接口,Object类的clone()方法会在运行时抛出CloneNotSupportedException异常。所有数组类型自动实现了Cloneable接口。
浅复制(Shallow Copy)和深度复制(Deep Copy)是两种复制策略。浅复制是指在复制过程中,仅仅复制对象的值,而并不会复制被成员变量引用的对象。更具体的说,如果成员变量是基本数据类型(Primitive Data Type),浅复制会复制这些成员变量的数值。如果成员变量是引用类型(Reference),浅复制会复制这些引用的数值(类似于C/C++语言中复制指针的值)。因此,在复制完成后,新旧对象中引用类型的成员变量会指向相同的对象。
例如,给定以下的Car类,对象c1.brand和c2.brand引用的是同一个字符串对象。
// Brand类抽象轿车的品牌
public class Brand implements Cloneable {
public String name = null;
public String description = null;
public Brand(String name, String description) {
this.name = name;
this.description = description;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// Car类抽象轿车
public class Car implements Cloneable {
public int price;
public Brand brand = null;
public Car(int price, Brand brand) {
this.price = price;
this.brand = brand;
}
public static void main(String[] args) {
try {
Car c1 = new Car(10000, new Brand("Ford", ""));
Car c2 = (Car)c1.clone(); //如果这是浅复制(Shallow Copy)
System.out.println(c1.brand == c2.brand); // 输出 True
} catch (CloneNotSupportedException ex) {
System.out.println(ex.getMessage());
}
}
}
深度复制则执行“深度”复制,即对引用类型的成员变量,深度复制会复制一个新的对象,复制后的成员变量会指向这个新的对象。
public class Car implements CLoneable {
public int price;
public Brand brand = null;
public Car(int price, Brand brand) {
this.price = price;
this.brand = brand;
}
public static void main(String[] args) {
Car c1 = new Car(10000, "Ford");
Car c2 = c1.clone(); //如果这是深度复制(Deep Copy)
System.out.println(c1.brand == c2.brand); // 输出 False
}
}
如图一的左侧示例所示,在浅复制过程结束后,c1.brand和c2.brand指向的是同一个Brand对象。而在右侧的深度拷贝示例中,在深度复制过程结束后,c1.brand和c2.brand指向的是两个不同的Brand对象;但这两个对象的内容相同。
图一 浅复制(Shallow Copy) vs. 深度复制(Deep Copy)
当上述的复制机制应用于继承中时,对象复制的过程将会变得比较复杂。当使用默认clone()成员方法时,使用的是浅复制。如果在类中覆盖了clone()方法,则可实现深度复制或者自定义的复制过程。在这里,开发人员需要注意的是:
如下面的例子所示,Car类自定义了一个深度复制的clone()方法。
public class Car implements Cloneable {
public int price;
public Brand brand = null;
public Object clone() throws CloneNotSupportedException {
try {
Car car = Car.class.cast(super.clone());
// super.clone() 已经复制了price成员变量,所以不再需要给price赋值
//car.price = this.price;
car.brand = Brand.class.cast(this.brand.clone());
return car;
} catch (CloneNotSupportedException ex) {
throw ex;
} catch (Exception ex) {
throw new CloneNotSupportedException(ex.getMessage());
}
}
}
在理解了Java对象复制机制后,我们再来看看对其的批评,因为该机制的设计有着许多不合理的地方。
Cloneable c1 = new Car();
Cloneable c2 = c1.clone(); //错误,Cloneable接口没有声明clone()方法。
为了避免clone()方法带来的复杂性,许多开发人员选择了其他的方法构造对象。在这里,我们仅简单的介绍一下这些方法。详细的内容会在后续的章节中介绍。
public class Vehicle {
public int age;
public Vehicle(Vehicle v) {
this.age = this.v;
}
}
public class Car extends Vehicle {
public int price;
public Brand brand = null;
public Car(Car c) {
super(c); // 调用父类的拷贝构造函数,初始化父类中的成员变量
this.price = c.price;
this.brand = c.brand; // 这里可以选择浅拷贝或者深拷贝
}
}
public class Car {
public int price;
public Brand brand;
public static Car make(Car c) {
Car newCar = new Car();
// 成员变量赋值
newCar.price = c.price;
newCar.brand = new Brand(c.brand); // 这里可以选择浅拷贝或者深拷贝
return newCar;
}
}
public class Car {
public int price;
public Brand brand = null;
Car(int price, Brand brand) {
this.price = price;
this.brand = brand;
}
// Car类的静态方法,用于构造一个CarBuilder对象
public static CarBuilder builder() {
return new CarBuilder();
}
// Car类的一个内部类,用于构造Car对象
public static class CarBuilder {
private int price;
private Brand brand;
// 默认构造函数,被Car.builder()静态方法用于创建CarBuilder对象
CarBuilder() {
}
// price() 和 brand() 成员方法用于保存price和brand的值
// 返回this对象,使得调用者可以使用函数调用链。
public CarBuilder price(int price) {
this.price = price;
return this;
}
public CarBuilder brand(Brand brand) {
this.brand = brand;
return this;
}
// 最后由build()成员函数构造Car类的对象
public Car build() {
return new Car(this.price, this.brand);
}
}
public static void main(String[] args) {
Car car = Car.builder().price(10000).brand(new Brand("Ford", "...")).build();
}
}
本章介绍了Java语言中的对象复制机制,以及一些替代方法。因为在clone()方法的设计上出现了较大的缺陷,开发人员逐渐的使用工厂类或者builder类来复制或者创建对象。因为是在Object类中提供的clone()方法,如果Java语言的设计者修改对象复制机制会造成严重的版本兼容性的问题,因此,clone()方法仍然保留至今。笔者也希望在未来的版本中,Java能较好的解决这个问题。
注册用户登陆后可留言