2010-11-16 12 views
7

Tengo un código que realiza una copia profunda usando Object.clone, pero estoy tratando de reescribirlo utilizando la técnica de creación de copia más "aceptable". A continuación hay dos ejemplos simples de lo que estoy tratando de hacer, el primero usando clon y el segundo usando un constructor de copia.Forma correcta de copiar en profundidad con el constructor de copia en lugar de Object.clone

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

copia profunda usando 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, el la alternativa de clon requiere menos código y menos lanzamientos (en este caso particular, al menos).

Agradecería los comentarios sobre la alternativa del constructor de copias. ¿Lo harías de manera diferente? Gracias.

Respuesta

3

En lugar de:

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

preferiría:

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

que los que llaman no tienen que manejar una excepción que nunca puede ocurrir, y no tienen que emitir.

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

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 usted no necesita ningún moldes.

Finalmente, tenga en cuenta que tanto la clonación como el método copy-constructor tienen la misma API pública (no importa demasiado si el método se llama clone() o deepCopy()), por lo que el enfoque que utilizará 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: Usar clon si solo quiere una copia idéntica, use copy-constructors si su interlocutor puede querer solicitar una instancia de un tipo específico.

1

Tenga en cuenta que en Person.deepCopy del enfoque del constructor de copias, la clase Person debe probar todas sus subclases de forma explícita. Este es un problema fundamental de diseño, mantenimiento de código y prueba: evitaría la clonación exitosa si alguien introduce una nueva subclase de Person, olvidando o no pudiendo actualizar Person.deepCopy. El método .clone() evita este problema al proporcionar un método virtual (clone).

+2

Olvidar o no poder actualizar Person.deepCopy no son problemas en comparación. Es decir, puede enfrentar problemas muy similares con la alternativa de clonación. Por ejemplo, si alguien crea un nuevo "administrador de clase extends Persona", deberá recordar implementar Administrator.clone (suponiendo que una copia de campo por campo no realiza una copia profunda). No poder actualizar Person.deepCopy puede manejarse sobrescribiéndolo en la subclase. Y sí, debes recordar hacer esto, pero nuevamente ese es el mismo problema con la alternativa de clonación. – vocaro

1

Una ventaja de un enfoque basado en clones es que, si se implementa correctamente, los tipos derivados que no requieren un comportamiento especial cuando se clonan no requerirán un código de clonación especial. A propósito, tiendo a pensar que las clases que exponen un método de clonación generalmente no deberían ser heredables; en su lugar, una clase base debería admitir la clonación como un método protegido, y una clase derivada debería admitir la clonación a través de una interfaz.Si un objeto no admitirá la clonación, no debería lanzar una excepción desde una API de clonación; en cambio, el objeto no debe tener una API de clonación.

Cuestiones relacionadas