Pregunta

Tengo algo de código que realiza una copia profunda usando Object.clone, pero estoy tratando de volver a escribir utilizando la técnica constructor de copia más "aceptable". A continuación se presentan dos ejemplos sencillos de lo que estoy tratando de hacer, el primer clon usando y la segunda utilizando un constructor de copia.

Deep copiar utilizando clon

 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 copia utilizando constructor de copia

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

Lo que me parece interesante es que a pesar de todo lo dicho sobre los males de la clonación en Java, la alternativa clon requiere menos código y un menor número de moldes (en este caso particular, por lo menos).

apreciaría retroalimentación sobre la alternativa constructor de copia. ¿Lo haría de manera diferente? Gracias.

¿Fue útil?

Solución

En lugar de:

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

Yo prefiero:

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

así que las personas que llaman no tienen que manejar una excepción que nunca puede ocurrir, y no tienen que desechar.

En el enfoque de constructor de copia, la conmutación tipo se maneja mejor polimórficamente:

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

Ahora el compilador puede comprobar que ha proporcionado copia profunda para todos los subtipos, y no se necesita ningún moldes.

Por último, señalar que tanto la clonación y constructor copia enfoque tienen la misma API pública (si el método se llama clone() o deepCopy() no importa mucho), por lo que se acercan a su uso es un detalle de implementación. El enfoque de constructor de copia es más prolija y cuando proporcione tanto un constructor y un método de llamar a ese constructor, pero se puede generalizar más fácilmente a una instalación de conversión de tipo general, permitiendo cosas como:

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

Recomendación:. Uso clon si solo deseas una copia idéntica de uso de copia-constructores si la persona que llama podría solicitar una instancia de un tipo específico

Otros consejos

Tenga en cuenta que en Person.deepCopy del enfoque constructor de copia, la clase Person tiene a prueba para todas sus subclases explícitamente. Este es un tema fundamental de diseño, el mantenimiento del código y pruebas: se evitaría la clonación exitosa si alguien introduce una nueva subclase de Person, olvidar o ser incapaz de actualización Person.deepCopy. El método .clone() evita este problema proporcionando un método virtual (clone).

Una de las ventajas de un enfoque basado en el clon es que, si se aplica adecuadamente, tipos de derivados que, en sí requieren un comportamiento especial cuando se clona no requerirá código de clonación especial. Por cierto, tiendo a pensar que las clases que exponen un método de clonación generalmente no deben ser heredables; en cambio, una clase base debe soportes clonación como un método protegido, y una clase derivada debe apoyar la clonación a través de una interfaz. Si un objeto no apoyará la clonación, no se debe lanzar una excepción de una API Clon; en cambio, el objeto no debe tener un API clon.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top