使用复制构造函数而不是Object.clone进行深层复制的正确方法

我有一些代码使用Object.clone执行深层复制,但我正在尝试使用更“可接受”的复制构造函数重写它。 下面是我正在尝试做的两个简单示例,第一个使用克隆,第二个使用复制构造函数。

使用克隆的深层复制

import java.util.*; abstract class Person implements Cloneable { String name; public Object clone() throws CloneNotSupportedException { return super.clone(); } } class Teacher extends Person implements Cloneable { int courses; public String toString() { return name + ": courses=" + courses; } } class Student extends Person implements Cloneable { double gpa; public String toString() { return name + ": gpa=" + gpa; } } public class DeepCopy_Clone { private static List deepCopy(List people) throws CloneNotSupportedException { List copy = new ArrayList(); for (Person person : people) { copy.add((Person)person.clone()); } return copy; } public static void main(String[] args) throws CloneNotSupportedException { ArrayList people = new ArrayList(); Teacher teacher = new Teacher(); teacher.name = "Teacher"; teacher.courses = 5; people.add(teacher); Student student = new Student(); student.name = "Student"; student.gpa = 4.0; people.add(student); List peopleCopy = deepCopy(people); // Invalidate the original data to prove a deep copy occurred teacher.name = null; teacher.courses = -1; student.name = null; student.gpa = -1; for (Person person : peopleCopy) { System.out.println(person.toString()); } } } 

使用复制构造函数的深层复制

  import java.util.*; abstract class Person { String name; public Person() {} public Person(Person other) { this.name = other.name; } public Person deepCopy() { if (this instanceof Teacher) { return new Teacher((Teacher)this); } else if (this instanceof Student) { return new Student((Student)this); } throw new Error("Unknown type of person"); } } class Teacher extends Person { int courses; public Teacher() {} public Teacher(Teacher other) { super(other); this.courses = other.courses; } public String toString() { return name + ": courses=" + courses; } } class Student extends Person { double gpa; public Student() {} public Student(Student other) { super(other); this.gpa = other.gpa; } public String toString() { return name + ": gpa=" + gpa; } } public class DeepCopy_ConstructorAlternative { private static List deepCopy(List people) { List copy = new ArrayList(); for (Person person : people) { copy.add(person.deepCopy()); } return copy; } public static void main(String[] args) { ArrayList people = new ArrayList(); Teacher teacher = new Teacher(); teacher.name = "Teacher"; teacher.courses = 5; people.add(teacher); Student student = new Student(); student.name = "Student"; student.gpa = 4.0; people.add(student); List peopleCopy = deepCopy(people); // Invalidate the original data to prove a deep copy occurred teacher.name = null; teacher.courses = -1; student.name = null; student.gpa = -1; for (Person person : peopleCopy) { System.out.println(person.toString()); } } } 

我觉得有趣的是,尽管所有关于Java中克隆的邪恶的讨论,克隆替代方案需要更少的代码和更少的强制转换(至少在这种情况下)。

我很感激复制构造函数的反馈。 你会以不同的方式做到吗? 谢谢。

代替:

  public Object clone() throws CloneNotSupportedException { return super.clone(); } 

我更喜欢:

 public Person clone() { try { return (Person) clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException("This should be impossible ..."); } } 

所以调用者不必处理永远不会发生的exception,也不必进行强制转换。

在复制构造方法中,类型切换可以更好地处理多态:

 abstract class Person { ... public abstract Person deepCopy(); } class Student { ... public Student deepCopy() { return new Student(this); } } class Teacher { ... public Teacher deepCopy() { return new Teacher(this); } } 

现在,编译器可以检查您是否为所有子类型提供了深层副本,并且您不需要任何强制转换。

最后,请注意,克隆和复制构造方法都具有相同的公共API(无论方法名为clone()还是deepCopy()都无关紧要),因此您使用的方法是实现细节。 复制构造方法更加冗长,因为您提供了构造函数和调用该构造函数的方法,但它可以更容易地推广到通用类型转换工具,允许以下内容:

 public Teacher(Person p) { ... say("Yay, I got a job"); } 

建议:如果只需要相同的副本,请使用clone,如果调用者可能希望请求特定类型的实例,请使用copy-constructors。

请注意,在复制构造方法的Person.deepCopy中, Person类必须显式地测试其所有子类。 这是一个基本的设计,代码维护和测试问题:如果有人引入Person的新子类,忘记或无法更新Person.deepCopy ,它将阻止成功克隆。 .clone()方法通过提供虚方法( clone )来避免此问题。

基于克隆的方法的一个优点是,如果正确实现,在克隆时本身不需要特殊行为的派生类型将不需要特殊的克隆代码。 顺便说一句,我倾向于认为暴露克隆方法的类通常不应该是可inheritance的; 相反,基类应该支持克隆作为受保护的方法,派生类应该通过接口支持克隆。 如果一个对象不支持克隆,它不应该从克隆API中抛出exception; 相反,该对象不应该具有克隆API。