Question

J'ai un code qui effectue une copie en profondeur avec Object.clone, mais je suis en train de le réécrire en utilisant la plus technique du constructeur de copie « acceptable ». Voici deux exemples simples de ce que je suis en train de le faire, le premier clone à l'aide et le second en utilisant un constructeur de copie.

Deep copier à l'aide 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 copie à l'aide constructeur copie

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

Ce que je trouve intéressant est que, malgré tous les discours sur les maux de clonage en Java, l'alternative clone nécessite moins de code et moins moulages (dans ce cas particulier, au moins).

J'apprécions les commentaires sur l'alternative constructeur de copie. Souhaitez-vous faire différemment? Merci.

Était-ce utile?

La solution

Au lieu de:

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

Je préfère:

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

afin que les appelants ne doivent pas gérer une exception qui ne peut jamais se produire, et ne pas à lancer.

Dans l'approche du constructeur de copie, la commutation de type est mieux traitée polymorphe:

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

le compilateur peut vérifier que vous avez fourni copie en profondeur pour tous les sous-types, et vous n'avez pas besoin moulages.

Enfin, notez que le clonage et à la fois l'approche copier-constructeur ont la même api publique (si la méthode est appelée clone() ou deepCopy() importe peu), de sorte que vous vous approchez de l'utilisation est un détail de mise en œuvre. L'approche constructeur de copie est plus bavard que vous fournissez à la fois un constructeur et une méthode d'appel que le constructeur, mais il peut être plus facilement généralisé à une installation de conversion de type général, ce qui permet des choses comme:

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

Recommandation:. Utiliser clone si vous voulez seulement une copie identique, utilisez copie-constructeurs si votre interlocuteur pourrait vouloir demander une instance d'un type spécifique

Autres conseils

S'il vous plaît noter que dans Person.deepCopy de l'approche du constructeur de copie, la classe Person doit tester pour toutes ses sous-classes explicitement. Ceci est une question fondamentale conception, la maintenance du code et de test: il empêcherait le succès du clonage si quelqu'un introduit une nouvelle sous-classe de Person, oubliant ou être incapable de mettre à jour Person.deepCopy. Le procédé de .clone() évite ce problème en fournissant un procédé virtuel (clone).

L'un des avantages d'une approche basée clone est que, si elles sont correctement mis en œuvre, les types dérivés qui ne sont pas eux-mêmes exigent un comportement particulier lorsqu'ils clonés ne nécessitera pas le code de clonage spécial. Soit dit en passant, je tends à penser que les classes qui exposent une méthode de clonage ne doivent généralement pas être héritable; à la place, une classe de base doit supports de clonage comme méthode protégée, et une classe dérivée doit soutenir le clonage via une interface. Si un objet ne soutiendra pas le clonage, il ne devrait pas lancer une exception d'une API Clone; au contraire, l'objet ne doit pas avoir une API clone.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top