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

发布于 2024-10-03 00:45:19 字数 3977 浏览 4 评论 0原文

我有一些使用 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 中克隆的弊端,但克隆替代方案需要更少的代码和更少的转换(至少在这个特殊情况下)。

我希望得到有关复制构造函数替代方案的反馈。你会采取不同的做法吗?谢谢。

I have some code that performs a deep copy using Object.clone, but I'm trying to rewrite it using the more "acceptable" copy constructor technique. Below are two simple examples of what I'm trying to do, the first using clone and the second using a copy constructor.

Deep copy using 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());
         }
     }
 }

Deep copy using copy constructor

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

What I find interesting is that despite all the talk about the evils of cloning in Java, the clone alternative requires less code and fewer casts (in this particular case, at least).

I'd appreciate feedback on the copy constructor alternative. Would you do it any differently? Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

放低过去 2024-10-10 00:45:19

而不是:

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

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

Instead of:

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

I'd prefer:

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

so callers don't have to handle an exception that can never occur, and don't have to cast.

In the copy-constructor approach, the type switching is better handled polymorphically:

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

now the compiler can check that you have provided deep copy for all subtypes, and you don't need any casts.

Finally, note that both the cloning and copy-constructor approach have the same public api (whether the method is called clone() or deepCopy() doesn't matter much), so which approach you use is an implementation detail. The copy-constructor approach is more verbose as you provide both a constructor and a method calling that constructor, but it can be more easily generalized to a general type conversion facility, allowing things like:

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

Recommendation: Use clone if you only want an identical copy, use copy-constructors if your caller might wish to request an instance of a specific type.

驱逐舰岛风号 2024-10-10 00:45:19

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

Please note that in Person.deepCopy of the copy constructor approach, the Person class has to test for all its subclasses explicitly. This is a fundamental design, code maintenance and testing issue: it would prevent successful cloning if someone introduces a new subclass of Person, forgetting or being unable to update Person.deepCopy. The .clone() method avoids this problem by providing a virtual method (clone).

枕头说它不想醒 2024-10-10 00:45:19

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

One advantage of a clone-based approach is that, if properly implemented, derived types which do not themselves require special behavior when cloned won't require special cloning code. Incidentally, I tend to think that classes which expose a cloning method should generally not be inheritable; instead, a base class should supports cloning as a protected method, and a derived class should support cloning via an interface. If an object won't support cloning, it shouldn't throw an exception from a Clone API; instead, the object shouldn't HAVE a clone API.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文