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 CloneNotSupportedEx开发者_开发技巧ception {
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.
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.
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
).
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.
精彩评论