【问题标题】:Proper way to deep copy with copy constructor instead of Object.clone使用复制构造函数而不是 Object.clone 进行深度复制的正确方法
【发布时间】:2011-05-11 02:38:16
【问题描述】:

我有一些使用 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<Person> deepCopy(List<Person> people) throws CloneNotSupportedException {
         List<Person> copy = new ArrayList<Person>();
         for (Person person : people) {
             copy.add((Person)person.clone());
         }
         return copy;
     }

     public static void main(String[] args) throws CloneNotSupportedException {
         ArrayList<Person> people = new ArrayList<Person>();

         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<Person> 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<Person> deepCopy(List<Person> people) {
         List<Person> copy = new ArrayList<Person>();
         for (Person person : people) {
             copy.add(person.deepCopy());
         }
         return copy;
     }

     public static void main(String[] args) {
         ArrayList<Person> people = new ArrayList<Person>();

         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<Person> 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 中克隆的弊端,但克隆替代方案需要更少的代码和更少的强制转换(至少在这种特殊情况下是这样)。

我很感激有关复制构造函数替代方案的反馈。你会做不同的事情吗?谢谢。

【问题讨论】:

    标签: java clone copy-constructor deep-copy cloneable


    【解决方案1】:

    代替:

     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 ...");
        }
    }
    

    因此调用者不必处理永远不会发生的异常,也不必强制转换。

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

    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");
    }
    

    建议:如果您只想要一个相同的副本,请使用克隆,如果您的调用者可能希望请求特定类型的实例,请使用复制构造函数。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 相比之下,忘记或无法更新 Person.deepCopy 不是问题。也就是说,您可能会面临与克隆替代方案非常相似的问题。例如,如果有人创建了一个新的“类 Administrator extends Person”,他必须记住实现 Administrator.clone(假设逐字段复制不执行深层复制)。无法更新 Person.deepCopy 可以通过在子类中覆盖它来处理。是的,您必须记住这样做,但这与克隆替代方案相同。
      【解决方案3】:

      基于克隆的方法的一个优点是,如果实施得当,在克隆时本身不需要特殊行为的派生类型将不需要特殊的克隆代码。顺便说一句,我倾向于认为公开克隆方法的类通常不应该是可继承的。相反,基类应支持将克隆作为受保护的方法,派生类应支持通过接口进行克隆。如果对象不支持克隆,则不应从 Clone API 抛出异常;相反,该对象不应具有克隆 API。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-26
        相关资源
        最近更新 更多