Proper way to deep copy with copy constructor instead of Object.clone
Asked Answered
P

3

7

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.

Pustule answered 16/11, 2010 at 21:56 Comment(0)
M
3

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.

Mamie answered 16/11, 2010 at 22:21 Comment(0)
H
1

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).

Hereditament answered 16/11, 2010 at 22:3 Comment(1)
Forgetting or being unable to update Person.deepCopy are non-issues by comparison. That is, you can face very similar issues with the clone alternative. For instance, if someone creates a new "class Administrator extends Person", he will have to remember to implement Administrator.clone (assuming a field-by-field copy does not perform a deep copy). Not being able to update Person.deepCopy can be handled by overriding it in the subclass. And yes, you do have to remember to do this, but again that's the same issue with the clone alternative.Pustule
M
1

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.

Misshape answered 22/11, 2010 at 23:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.